diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/Gemfile ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/Gemfile --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/Gemfile 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/Gemfile 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -source "https://rubygems.org" - -gemspec - -gem "rake" -gem "test-unit" diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/LICENSE.txt ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/LICENSE.txt --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/LICENSE.txt 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/README.md ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/README.md --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/README.md 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -# Net::FTP - -This class implements the File Transfer Protocol. If you have used a -command-line FTP program, and are familiar with the commands, you will be -able to use this class easily. Some extra features are included to take -advantage of Ruby's style and strengths. - -## Installation - -Add this line to your application's Gemfile: - -```ruby -gem 'net-ftp' -``` - -And then execute: - - $ bundle install - -Or install it yourself as: - - $ gem install net-ftp - -## Usage - -### Example 1 - -```ruby -ftp = Net::FTP.new('example.com') -ftp.login -files = ftp.chdir('pub/lang/ruby/contrib') -files = ftp.list('n*') -ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024) -ftp.close -``` - -### Example 2 - -```ruby -Net::FTP.open('example.com') do |ftp| - ftp.login - files = ftp.chdir('pub/lang/ruby/contrib') - files = ftp.list('n*') - ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024) -end -``` - -## Development - -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. - -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). - -## Contributing - -Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-ftp. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/Rakefile ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/Rakefile --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/Rakefile 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/Rakefile 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -require "bundler/gem_tasks" -require "rake/testtask" - -Rake::TestTask.new(:test) do |t| - t.libs << "test/lib" - t.ruby_opts << "-rhelper" - t.test_files = FileList["test/**/test_*.rb"] -end - -task :default => :test diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/lib/net/ftp.rb ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/lib/net/ftp.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/lib/net/ftp.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/lib/net/ftp.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,1556 +0,0 @@ -# frozen_string_literal: true -# -# = net/ftp.rb - FTP Client Library -# -# Written by Shugo Maeda . -# -# Documentation by Gavin Sinclair, sourced from "Programming Ruby" (Hunt/Thomas) -# and "Ruby In a Nutshell" (Matsumoto), used with permission. -# -# This library is distributed under the terms of the Ruby license. -# You can freely distribute/modify this library. -# -# It is included in the Ruby standard library. -# -# See the Net::FTP class for an overview. -# - -require "socket" -require "monitor" -require "net/protocol" -require "time" -begin - require "openssl" -rescue LoadError -end - -module Net - - # :stopdoc: - class FTPError < StandardError; end - class FTPReplyError < FTPError; end - class FTPTempError < FTPError; end - class FTPPermError < FTPError; end - class FTPProtoError < FTPError; end - class FTPConnectionError < FTPError; end - # :startdoc: - - # - # This class implements the File Transfer Protocol. If you have used a - # command-line FTP program, and are familiar with the commands, you will be - # able to use this class easily. Some extra features are included to take - # advantage of Ruby's style and strengths. - # - # == Example - # - # require 'net/ftp' - # - # === Example 1 - # - # ftp = Net::FTP.new('example.com') - # ftp.login - # files = ftp.chdir('pub/lang/ruby/contrib') - # files = ftp.list('n*') - # ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024) - # ftp.close - # - # === Example 2 - # - # Net::FTP.open('example.com') do |ftp| - # ftp.login - # files = ftp.chdir('pub/lang/ruby/contrib') - # files = ftp.list('n*') - # ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024) - # end - # - # == Major Methods - # - # The following are the methods most likely to be useful to users: - # - FTP.open - # - #getbinaryfile - # - #gettextfile - # - #putbinaryfile - # - #puttextfile - # - #chdir - # - #nlst - # - #size - # - #rename - # - #delete - # - class FTP < Protocol - include MonitorMixin - if defined?(OpenSSL::SSL) - include OpenSSL - include SSL - end - - # :stopdoc: - VERSION = "0.3.3" - FTP_PORT = 21 - CRLF = "\r\n" - DEFAULT_BLOCKSIZE = BufferedIO::BUFSIZE - @@default_passive = true - # :startdoc: - - # When +true+, transfers are performed in binary mode. Default: +true+. - attr_reader :binary - - # When +true+, the connection is in passive mode. Default: +true+. - attr_accessor :passive - - # When +true+, use the IP address in PASV responses. Otherwise, it uses - # the same IP address for the control connection. Default: +false+. - attr_accessor :use_pasv_ip - - # When +true+, all traffic to and from the server is written - # to +$stdout+. Default: +false+. - attr_accessor :debug_mode - - # Sets or retrieves the output stream for debugging. - # Output stream will be used only when +debug_mode+ is set to true. - # The default value is +$stdout+. - attr_accessor :debug_output - - # Sets or retrieves the +resume+ status, which decides whether incomplete - # transfers are resumed or restarted. Default: +false+. - attr_accessor :resume - - # Number of seconds to wait for the connection to open. Any number - # may be used, including Floats for fractional seconds. If the FTP - # object cannot open a connection in this many seconds, it raises a - # Net::OpenTimeout exception. The default value is +nil+. - attr_accessor :open_timeout - - # Number of seconds to wait for the TLS handshake. Any number - # may be used, including Floats for fractional seconds. If the FTP - # object cannot complete the TLS handshake in this many seconds, it - # raises a Net::OpenTimeout exception. The default value is +nil+. - # If +ssl_handshake_timeout+ is +nil+, +open_timeout+ is used instead. - attr_accessor :ssl_handshake_timeout - - # Number of seconds to wait for one block to be read (via one read(2) - # call). Any number may be used, including Floats for fractional - # seconds. If the FTP object cannot read data in this many seconds, - # it raises a Timeout::Error exception. The default value is 60 seconds. - attr_reader :read_timeout - - # Setter for the read_timeout attribute. - def read_timeout=(sec) - @sock.read_timeout = sec - @read_timeout = sec - end - - # The server's welcome message. - attr_reader :welcome - - # The server's last response code. - attr_reader :last_response_code - alias lastresp last_response_code - - # The server's last response. - attr_reader :last_response - - # When +true+, connections are in passive mode per default. - # Default: +true+. - def self.default_passive=(value) - @@default_passive = value - end - - # When +true+, connections are in passive mode per default. - # Default: +true+. - def self.default_passive - @@default_passive - end - - # - # A synonym for FTP.new, but with a mandatory host parameter. - # - # If a block is given, it is passed the +FTP+ object, which will be closed - # when the block finishes, or when an exception is raised. - # - def FTP.open(host, *args) - if block_given? - ftp = new(host, *args) - begin - yield ftp - ensure - ftp.close - end - else - new(host, *args) - end - end - - # :call-seq: - # Net::FTP.new(host = nil, options = {}) - # - # Creates and returns a new +FTP+ object. If a +host+ is given, a connection - # is made. - # - # +options+ is an option hash, each key of which is a symbol. - # - # The available options are: - # - # port:: Port number (default value is 21) - # ssl:: If +options+[:ssl] is true, then an attempt will be made - # to use SSL (now TLS) to connect to the server. For this - # to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] - # extensions need to be installed. If +options+[:ssl] is a - # hash, it's passed to OpenSSL::SSL::SSLContext#set_params - # as parameters. - # private_data_connection:: If true, TLS is used for data connections. - # Default: +true+ when +options+[:ssl] is true. - # implicit_ftps:: If true, TLS is established on initial connection. - # Default: +false+ - # username:: Username for login. If +options+[:username] is the string - # "anonymous" and the +options+[:password] is +nil+, - # "anonymous@" is used as a password. - # password:: Password for login. - # account:: Account information for ACCT. - # passive:: When +true+, the connection is in passive mode. Default: - # +true+. - # open_timeout:: Number of seconds to wait for the connection to open. - # See Net::FTP#open_timeout for details. Default: +nil+. - # read_timeout:: Number of seconds to wait for one block to be read. - # See Net::FTP#read_timeout for details. Default: +60+. - # ssl_handshake_timeout:: Number of seconds to wait for the TLS - # handshake. - # See Net::FTP#ssl_handshake_timeout for - # details. Default: +nil+. - # use_pasv_ip:: When +true+, use the IP address in PASV responses. - # Otherwise, it uses the same IP address for the control - # connection. Default: +false+. - # debug_mode:: When +true+, all traffic to and from the server is - # written to +$stdout+. Default: +false+. - # - def initialize(host = nil, user_or_options = {}, passwd = nil, acct = nil) - super() - begin - options = user_or_options.to_hash - rescue NoMethodError - # for backward compatibility - options = {} - options[:username] = user_or_options - options[:password] = passwd - options[:account] = acct - end - @host = nil - if options[:ssl] - unless defined?(OpenSSL::SSL) - raise "SSL extension not installed" - end - ssl_params = options[:ssl] == true ? {} : options[:ssl] - @ssl_context = SSLContext.new - @ssl_context.set_params(ssl_params) - if defined?(VerifyCallbackProc) - @ssl_context.verify_callback = VerifyCallbackProc - end - @ssl_context.session_cache_mode = - OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | - OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE - @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess } - @ssl_session = nil - if options[:private_data_connection].nil? - @private_data_connection = true - else - @private_data_connection = options[:private_data_connection] - end - if options[:implicit_ftps].nil? - @implicit_ftps = false - else - @implicit_ftps = options[:implicit_ftps] - end - else - @ssl_context = nil - if options[:private_data_connection] - raise ArgumentError, - "private_data_connection can be set to true only when ssl is enabled" - end - @private_data_connection = false - if options[:implicit_ftps] - raise ArgumentError, - "implicit_ftps can be set to true only when ssl is enabled" - end - @implicit_ftps = false - end - @binary = true - if options[:passive].nil? - @passive = @@default_passive - else - @passive = options[:passive] - end - if options[:debug_mode].nil? - @debug_mode = false - else - @debug_mode = options[:debug_mode] - end - @debug_output = $stdout - @resume = false - @bare_sock = @sock = NullSocket.new - @logged_in = false - @open_timeout = options[:open_timeout] - @ssl_handshake_timeout = options[:ssl_handshake_timeout] - @read_timeout = options[:read_timeout] || 60 - @use_pasv_ip = options[:use_pasv_ip] || false - if host - connect(host, options[:port] || FTP_PORT) - if options[:username] - login(options[:username], options[:password], options[:account]) - end - end - end - - # A setter to toggle transfers in binary mode. - # +newmode+ is either +true+ or +false+ - def binary=(newmode) - if newmode != @binary - @binary = newmode - send_type_command if @logged_in - end - end - - # Sends a command to destination host, with the current binary sendmode - # type. - # - # If binary mode is +true+, then "TYPE I" (image) is sent, otherwise "TYPE - # A" (ascii) is sent. - def send_type_command # :nodoc: - if @binary - voidcmd("TYPE I") - else - voidcmd("TYPE A") - end - end - private :send_type_command - - # Toggles transfers in binary mode and yields to a block. - # This preserves your current binary send mode, but allows a temporary - # transaction with binary sendmode of +newmode+. - # - # +newmode+ is either +true+ or +false+ - def with_binary(newmode) # :nodoc: - oldmode = binary - self.binary = newmode - begin - yield - ensure - self.binary = oldmode - end - end - private :with_binary - - # Obsolete - def return_code # :nodoc: - warn("Net::FTP#return_code is obsolete and do nothing", uplevel: 1) - return "\n" - end - - # Obsolete - def return_code=(s) # :nodoc: - warn("Net::FTP#return_code= is obsolete and do nothing", uplevel: 1) - end - - # Constructs a socket with +host+ and +port+. - # - # If SOCKSSocket is defined and the environment (ENV) defines - # SOCKS_SERVER, then a SOCKSSocket is returned, else a Socket is - # returned. - def open_socket(host, port) # :nodoc: - return Timeout.timeout(@open_timeout, OpenTimeout) { - if defined? SOCKSSocket and ENV["SOCKS_SERVER"] - @passive = true - SOCKSSocket.open(host, port) - else - Socket.tcp(host, port) - end - } - end - private :open_socket - - def start_tls_session(sock) - ssl_sock = SSLSocket.new(sock, @ssl_context) - ssl_sock.sync_close = true - ssl_sock.hostname = @host if ssl_sock.respond_to? :hostname= - if @ssl_session && - Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout - # ProFTPD returns 425 for data connections if session is not reused. - ssl_sock.session = @ssl_session - end - ssl_socket_connect(ssl_sock, @ssl_handshake_timeout || @open_timeout) - if @ssl_context.verify_mode != VERIFY_NONE - ssl_sock.post_connection_check(@host) - end - return ssl_sock - end - private :start_tls_session - - # - # Establishes an FTP connection to host, optionally overriding the default - # port. If the environment variable +SOCKS_SERVER+ is set, sets up the - # connection through a SOCKS proxy. Raises an exception (typically - # Errno::ECONNREFUSED) if the connection cannot be established. - # - def connect(host, port = FTP_PORT) - debug_print "connect: #{host}:#{port}" - synchronize do - @host = host - @bare_sock = open_socket(host, port) - if @ssl_context - begin - unless @implicit_ftps - set_socket(BufferedSocket.new(@bare_sock, read_timeout: @read_timeout)) - voidcmd("AUTH TLS") - end - set_socket(BufferedSSLSocket.new(start_tls_session(@bare_sock), read_timeout: @read_timeout), @implicit_ftps) - if @private_data_connection - voidcmd("PBSZ 0") - voidcmd("PROT P") - end - rescue OpenSSL::SSL::SSLError, OpenTimeout - @sock.close - raise - end - else - set_socket(BufferedSocket.new(@bare_sock, read_timeout: @read_timeout)) - end - end - end - - # - # Set the socket used to connect to the FTP server. - # - # May raise FTPReplyError if +get_greeting+ is false. - def set_socket(sock, get_greeting = true) - synchronize do - @sock = sock - if get_greeting - voidresp - end - end - end - - # If string +s+ includes the PASS command (password), then the contents of - # the password are cleaned from the string using "*" - def sanitize(s) # :nodoc: - if s =~ /^PASS /i - return s[0, 5] + "*" * (s.length - 5) - else - return s - end - end - private :sanitize - - # Ensures that +line+ has a control return / line feed (CRLF) and writes - # it to the socket. - def putline(line) # :nodoc: - debug_print "put: #{sanitize(line)}" - if /[\r\n]/ =~ line - raise ArgumentError, "A line must not contain CR or LF" - end - line = line + CRLF - @sock.write(line) - end - private :putline - - # Reads a line from the sock. If EOF, then it will raise EOFError - def getline # :nodoc: - line = @sock.readline # if get EOF, raise EOFError - line.sub!(/(\r\n|\n|\r)\z/n, "") - debug_print "get: #{sanitize(line)}" - return line - end - private :getline - - # Receive a section of lines until the response code's match. - def getmultiline # :nodoc: - lines = [] - lines << getline - code = lines.last.slice(/\A([0-9a-zA-Z]{3})-/, 1) - if code - delimiter = code + " " - begin - lines << getline - end until lines.last.start_with?(delimiter) - end - return lines.join("\n") + "\n" - end - private :getmultiline - - # Receives a response from the destination host. - # - # Returns the response code or raises FTPTempError, FTPPermError, or - # FTPProtoError - def getresp # :nodoc: - @last_response = getmultiline - @last_response_code = @last_response[0, 3] - case @last_response_code - when /\A[123]/ - return @last_response - when /\A4/ - raise FTPTempError, @last_response - when /\A5/ - raise FTPPermError, @last_response - else - raise FTPProtoError, @last_response - end - end - private :getresp - - # Receives a response. - # - # Raises FTPReplyError if the first position of the response code is not - # equal 2. - def voidresp # :nodoc: - resp = getresp - if !resp.start_with?("2") - raise FTPReplyError, resp - end - end - private :voidresp - - # - # Sends a command and returns the response. - # - def sendcmd(cmd) - synchronize do - putline(cmd) - return getresp - end - end - - # - # Sends a command and expect a response beginning with '2'. - # - def voidcmd(cmd) - synchronize do - putline(cmd) - voidresp - end - end - - # Constructs and send the appropriate PORT (or EPRT) command - def sendport(host, port) # :nodoc: - remote_address = @bare_sock.remote_address - if remote_address.ipv4? - cmd = "PORT " + (host.split(".") + port.divmod(256)).join(",") - elsif remote_address.ipv6? - cmd = sprintf("EPRT |2|%s|%d|", host, port) - else - raise FTPProtoError, host - end - voidcmd(cmd) - end - private :sendport - - # Constructs a TCPServer socket - def makeport # :nodoc: - Addrinfo.tcp(@bare_sock.local_address.ip_address, 0).listen - end - private :makeport - - # sends the appropriate command to enable a passive connection - def makepasv # :nodoc: - if @bare_sock.remote_address.ipv4? - host, port = parse227(sendcmd("PASV")) - else - host, port = parse229(sendcmd("EPSV")) - end - return host, port - end - private :makepasv - - # Constructs a connection for transferring data - def transfercmd(cmd, rest_offset = nil) # :nodoc: - if @passive - host, port = makepasv - succeeded = false - begin - conn = open_socket(host, port) - if @resume and rest_offset - resp = sendcmd("REST " + rest_offset.to_s) - if !resp.start_with?("3") - raise FTPReplyError, resp - end - end - resp = sendcmd(cmd) - # skip 2XX for some ftp servers - resp = getresp if resp.start_with?("2") - if !resp.start_with?("1") - raise FTPReplyError, resp - end - succeeded = true - ensure - conn&.close if !succeeded - end - else - sock = makeport - begin - addr = sock.local_address - sendport(addr.ip_address, addr.ip_port) - if @resume and rest_offset - resp = sendcmd("REST " + rest_offset.to_s) - if !resp.start_with?("3") - raise FTPReplyError, resp - end - end - resp = sendcmd(cmd) - # skip 2XX for some ftp servers - resp = getresp if resp.start_with?("2") - if !resp.start_with?("1") - raise FTPReplyError, resp - end - conn, = sock.accept - sock.shutdown(Socket::SHUT_WR) rescue nil - sock.read rescue nil - ensure - sock.close - end - end - if @private_data_connection - return BufferedSSLSocket.new(start_tls_session(conn), - read_timeout: @read_timeout) - else - return BufferedSocket.new(conn, read_timeout: @read_timeout) - end - end - private :transfercmd - - # - # Logs in to the remote host. The session must have been - # previously connected. If +user+ is the string "anonymous" and - # the +password+ is +nil+, "anonymous@" is used as a password. If - # the +acct+ parameter is not +nil+, an FTP ACCT command is sent - # following the successful login. Raises an exception on error - # (typically Net::FTPPermError). - # - def login(user = "anonymous", passwd = nil, acct = nil) - if user == "anonymous" and passwd == nil - passwd = "anonymous@" - end - - resp = "" - synchronize do - resp = sendcmd('USER ' + user) - if resp.start_with?("3") - raise FTPReplyError, resp if passwd.nil? - resp = sendcmd('PASS ' + passwd) - end - if resp.start_with?("3") - raise FTPReplyError, resp if acct.nil? - resp = sendcmd('ACCT ' + acct) - end - end - if !resp.start_with?("2") - raise FTPReplyError, resp - end - @welcome = resp - send_type_command - @logged_in = true - end - - # - # Puts the connection into binary (image) mode, issues the given command, - # and fetches the data returned, passing it to the associated block in - # chunks of +blocksize+ characters. Note that +cmd+ is a server command - # (such as "RETR myfile"). - # - def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data - synchronize do - with_binary(true) do - begin - conn = transfercmd(cmd, rest_offset) - while data = conn.read(blocksize) - yield(data) - end - conn.shutdown(Socket::SHUT_WR) rescue nil - conn.read_timeout = 1 - conn.read rescue nil - ensure - conn.close if conn - end - voidresp - end - end - end - - # - # Puts the connection into ASCII (text) mode, issues the given command, and - # passes the resulting data, one line at a time, to the associated block. If - # no block is given, prints the lines. Note that +cmd+ is a server command - # (such as "RETR myfile"). - # - def retrlines(cmd) # :yield: line - synchronize do - with_binary(false) do - begin - conn = transfercmd(cmd) - while line = conn.gets - yield(line.sub(/\r?\n\z/, ""), !line.match(/\n\z/).nil?) - end - conn.shutdown(Socket::SHUT_WR) rescue nil - conn.read_timeout = 1 - conn.read rescue nil - ensure - conn.close if conn - end - voidresp - end - end - end - - # - # Puts the connection into binary (image) mode, issues the given server-side - # command (such as "STOR myfile"), and sends the contents of the file named - # +file+ to the server. If the optional block is given, it also passes it - # the data, in chunks of +blocksize+ characters. - # - def storbinary(cmd, file, blocksize, rest_offset = nil) # :yield: data - if rest_offset - file.seek(rest_offset, IO::SEEK_SET) - end - synchronize do - with_binary(true) do - begin - conn = transfercmd(cmd) - while buf = file.read(blocksize) - conn.write(buf) - yield(buf) if block_given? - end - conn.shutdown(Socket::SHUT_WR) rescue nil - conn.read_timeout = 1 - conn.read rescue nil - ensure - conn.close if conn - end - voidresp - end - end - rescue Errno::EPIPE - # EPIPE, in this case, means that the data connection was unexpectedly - # terminated. Rather than just raising EPIPE to the caller, check the - # response on the control connection. If getresp doesn't raise a more - # appropriate exception, re-raise the original exception. - getresp - raise - end - - # - # Puts the connection into ASCII (text) mode, issues the given server-side - # command (such as "STOR myfile"), and sends the contents of the file - # named +file+ to the server, one line at a time. If the optional block is - # given, it also passes it the lines. - # - def storlines(cmd, file) # :yield: line - synchronize do - with_binary(false) do - begin - conn = transfercmd(cmd) - while buf = file.gets - if buf[-2, 2] != CRLF - buf = buf.chomp + CRLF - end - conn.write(buf) - yield(buf) if block_given? - end - conn.shutdown(Socket::SHUT_WR) rescue nil - conn.read_timeout = 1 - conn.read rescue nil - ensure - conn.close if conn - end - voidresp - end - end - rescue Errno::EPIPE - # EPIPE, in this case, means that the data connection was unexpectedly - # terminated. Rather than just raising EPIPE to the caller, check the - # response on the control connection. If getresp doesn't raise a more - # appropriate exception, re-raise the original exception. - getresp - raise - end - - # - # Retrieves +remotefile+ in binary mode, storing the result in +localfile+. - # If +localfile+ is nil, returns retrieved data. - # If a block is supplied, it is passed the retrieved data in +blocksize+ - # chunks. - # - def getbinaryfile(remotefile, localfile = File.basename(remotefile), - blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data - f = nil - result = nil - if localfile - if @resume - rest_offset = File.size?(localfile) - f = File.open(localfile, "a") - else - rest_offset = nil - f = File.open(localfile, "w") - end - elsif !block_given? - result = String.new - end - begin - f&.binmode - retrbinary("RETR #{remotefile}", blocksize, rest_offset) do |data| - f&.write(data) - block&.(data) - result&.concat(data) - end - return result - ensure - f&.close - end - end - - # - # Retrieves +remotefile+ in ASCII (text) mode, storing the result in - # +localfile+. - # If +localfile+ is nil, returns retrieved data. - # If a block is supplied, it is passed the retrieved data one - # line at a time. - # - def gettextfile(remotefile, localfile = File.basename(remotefile), - &block) # :yield: line - f = nil - result = nil - if localfile - f = File.open(localfile, "w") - elsif !block_given? - result = String.new - end - begin - retrlines("RETR #{remotefile}") do |line, newline| - l = newline ? line + "\n" : line - f&.print(l) - block&.(line, newline) - result&.concat(l) - end - return result - ensure - f&.close - end - end - - # - # Retrieves +remotefile+ in whatever mode the session is set (text or - # binary). See #gettextfile and #getbinaryfile. - # - def get(remotefile, localfile = File.basename(remotefile), - blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data - if @binary - getbinaryfile(remotefile, localfile, blocksize, &block) - else - gettextfile(remotefile, localfile, &block) - end - end - - # - # Transfers +localfile+ to the server in binary mode, storing the result in - # +remotefile+. If a block is supplied, calls it, passing in the transmitted - # data in +blocksize+ chunks. - # - def putbinaryfile(localfile, remotefile = File.basename(localfile), - blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data - if @resume - begin - rest_offset = size(remotefile) - rescue Net::FTPPermError - rest_offset = nil - end - else - rest_offset = nil - end - f = File.open(localfile) - begin - f.binmode - if rest_offset - storbinary("APPE #{remotefile}", f, blocksize, rest_offset, &block) - else - storbinary("STOR #{remotefile}", f, blocksize, rest_offset, &block) - end - ensure - f.close - end - end - - # - # Transfers +localfile+ to the server in ASCII (text) mode, storing the result - # in +remotefile+. If callback or an associated block is supplied, calls it, - # passing in the transmitted data one line at a time. - # - def puttextfile(localfile, remotefile = File.basename(localfile), &block) # :yield: line - f = File.open(localfile) - begin - storlines("STOR #{remotefile}", f, &block) - ensure - f.close - end - end - - # - # Transfers +localfile+ to the server in whatever mode the session is set - # (text or binary). See #puttextfile and #putbinaryfile. - # - def put(localfile, remotefile = File.basename(localfile), - blocksize = DEFAULT_BLOCKSIZE, &block) - if @binary - putbinaryfile(localfile, remotefile, blocksize, &block) - else - puttextfile(localfile, remotefile, &block) - end - end - - # - # Sends the ACCT command. - # - # This is a less common FTP command, to send account - # information if the destination host requires it. - # - def acct(account) - cmd = "ACCT " + account - voidcmd(cmd) - end - - # - # Returns an array of filenames in the remote directory. - # - def nlst(dir = nil) - cmd = "NLST" - if dir - cmd = "#{cmd} #{dir}" - end - files = [] - retrlines(cmd) do |line| - files.push(line) - end - return files - end - - # - # Returns an array of file information in the directory (the output is like - # `ls -l`). If a block is given, it iterates through the listing. - # - def list(*args, &block) # :yield: line - cmd = "LIST" - args.each do |arg| - cmd = "#{cmd} #{arg}" - end - lines = [] - retrlines(cmd) do |line| - lines << line - end - if block - lines.each(&block) - end - return lines - end - alias ls list - alias dir list - - # - # MLSxEntry represents an entry in responses of MLST/MLSD. - # Each entry has the facts (e.g., size, last modification time, etc.) - # and the pathname. - # - class MLSxEntry - attr_reader :facts, :pathname - - def initialize(facts, pathname) - @facts = facts - @pathname = pathname - end - - standard_facts = %w(size modify create type unique perm - lang media-type charset) - standard_facts.each do |factname| - define_method factname.gsub(/-/, "_") do - facts[factname] - end - end - - # - # Returns +true+ if the entry is a file (i.e., the value of the type - # fact is file). - # - def file? - return facts["type"] == "file" - end - - # - # Returns +true+ if the entry is a directory (i.e., the value of the - # type fact is dir, cdir, or pdir). - # - def directory? - if /\A[cp]?dir\z/.match(facts["type"]) - return true - else - return false - end - end - - # - # Returns +true+ if the APPE command may be applied to the file. - # - def appendable? - return facts["perm"].include?(?a) - end - - # - # Returns +true+ if files may be created in the directory by STOU, - # STOR, APPE, and RNTO. - # - def creatable? - return facts["perm"].include?(?c) - end - - # - # Returns +true+ if the file or directory may be deleted by DELE/RMD. - # - def deletable? - return facts["perm"].include?(?d) - end - - # - # Returns +true+ if the directory may be entered by CWD/CDUP. - # - def enterable? - return facts["perm"].include?(?e) - end - - # - # Returns +true+ if the file or directory may be renamed by RNFR. - # - def renamable? - return facts["perm"].include?(?f) - end - - # - # Returns +true+ if the listing commands, LIST, NLST, and MLSD are - # applied to the directory. - # - def listable? - return facts["perm"].include?(?l) - end - - # - # Returns +true+ if the MKD command may be used to create a new - # directory within the directory. - # - def directory_makable? - return facts["perm"].include?(?m) - end - - # - # Returns +true+ if the objects in the directory may be deleted, or - # the directory may be purged. - # - def purgeable? - return facts["perm"].include?(?p) - end - - # - # Returns +true+ if the RETR command may be applied to the file. - # - def readable? - return facts["perm"].include?(?r) - end - - # - # Returns +true+ if the STOR command may be applied to the file. - # - def writable? - return facts["perm"].include?(?w) - end - end - - CASE_DEPENDENT_PARSER = ->(value) { value } - CASE_INDEPENDENT_PARSER = ->(value) { value.downcase } - DECIMAL_PARSER = ->(value) { value.to_i } - OCTAL_PARSER = ->(value) { value.to_i(8) } - TIME_PARSER = ->(value, local = false) { - unless /\A(?\d{4})(?\d{2})(?\d{2}) - (?\d{2})(?\d{2})(?\d{2}) - (?:\.(?\d{1,17}))?/x =~ value - value = value[0, 97] + "..." if value.size > 100 - raise FTPProtoError, "invalid time-val: #{value}" - end - usec = ".#{fractions}".to_r * 1_000_000 if fractions - Time.public_send(local ? :local : :utc, year, month, day, hour, min, sec, usec) - } - FACT_PARSERS = Hash.new(CASE_DEPENDENT_PARSER) - FACT_PARSERS["size"] = DECIMAL_PARSER - FACT_PARSERS["modify"] = TIME_PARSER - FACT_PARSERS["create"] = TIME_PARSER - FACT_PARSERS["type"] = CASE_INDEPENDENT_PARSER - FACT_PARSERS["unique"] = CASE_DEPENDENT_PARSER - FACT_PARSERS["perm"] = CASE_INDEPENDENT_PARSER - FACT_PARSERS["lang"] = CASE_INDEPENDENT_PARSER - FACT_PARSERS["media-type"] = CASE_INDEPENDENT_PARSER - FACT_PARSERS["charset"] = CASE_INDEPENDENT_PARSER - FACT_PARSERS["unix.mode"] = OCTAL_PARSER - FACT_PARSERS["unix.owner"] = DECIMAL_PARSER - FACT_PARSERS["unix.group"] = DECIMAL_PARSER - FACT_PARSERS["unix.ctime"] = TIME_PARSER - FACT_PARSERS["unix.atime"] = TIME_PARSER - - def parse_mlsx_entry(entry) - facts, pathname = entry.chomp.split(/ /, 2) - unless pathname - raise FTPProtoError, entry - end - return MLSxEntry.new( - facts.scan(/(.*?)=(.*?);/).each_with_object({}) { - |(factname, value), h| - name = factname.downcase - h[name] = FACT_PARSERS[name].(value) - }, - pathname) - end - private :parse_mlsx_entry - - # - # Returns data (e.g., size, last modification time, entry type, etc.) - # about the file or directory specified by +pathname+. - # If +pathname+ is omitted, the current directory is assumed. - # - def mlst(pathname = nil) - cmd = pathname ? "MLST #{pathname}" : "MLST" - resp = sendcmd(cmd) - if !resp.start_with?("250") - raise FTPReplyError, resp - end - line = resp.lines[1] - unless line - raise FTPProtoError, resp - end - entry = line.sub(/\A(250-| *)/, "") - return parse_mlsx_entry(entry) - end - - # - # Returns an array of the entries of the directory specified by - # +pathname+. - # Each entry has the facts (e.g., size, last modification time, etc.) - # and the pathname. - # If a block is given, it iterates through the listing. - # If +pathname+ is omitted, the current directory is assumed. - # - def mlsd(pathname = nil, &block) # :yield: entry - cmd = pathname ? "MLSD #{pathname}" : "MLSD" - entries = [] - retrlines(cmd) do |line| - entries << parse_mlsx_entry(line) - end - if block - entries.each(&block) - end - return entries - end - - # - # Renames a file on the server. - # - def rename(fromname, toname) - resp = sendcmd("RNFR #{fromname}") - if !resp.start_with?("3") - raise FTPReplyError, resp - end - voidcmd("RNTO #{toname}") - end - - # - # Deletes a file on the server. - # - def delete(filename) - resp = sendcmd("DELE #{filename}") - if resp.start_with?("250") - return - elsif resp.start_with?("5") - raise FTPPermError, resp - else - raise FTPReplyError, resp - end - end - - # - # Changes the (remote) directory. - # - def chdir(dirname) - if dirname == ".." - begin - voidcmd("CDUP") - return - rescue FTPPermError => e - if e.message[0, 3] != "500" - raise e - end - end - end - cmd = "CWD #{dirname}" - voidcmd(cmd) - end - - def get_body(resp) # :nodoc: - resp.slice(/\A[0-9a-zA-Z]{3} (.*)$/, 1) - end - private :get_body - - # - # Returns the size of the given (remote) filename. - # - def size(filename) - with_binary(true) do - resp = sendcmd("SIZE #{filename}") - if !resp.start_with?("213") - raise FTPReplyError, resp - end - return get_body(resp).to_i - end - end - - # - # Returns the last modification time of the (remote) file. If +local+ is - # +true+, it is returned as a local time, otherwise it's a UTC time. - # - def mtime(filename, local = false) - return TIME_PARSER.(mdtm(filename), local) - end - - # - # Creates a remote directory. - # - def mkdir(dirname) - resp = sendcmd("MKD #{dirname}") - return parse257(resp) - end - - # - # Removes a remote directory. - # - def rmdir(dirname) - voidcmd("RMD #{dirname}") - end - - # - # Returns the current remote directory. - # - def pwd - resp = sendcmd("PWD") - return parse257(resp) - end - alias getdir pwd - - # - # Returns system information. - # - def system - resp = sendcmd("SYST") - if !resp.start_with?("215") - raise FTPReplyError, resp - end - return get_body(resp) - end - - # - # Aborts the previous command (ABOR command). - # - def abort - line = "ABOR" + CRLF - debug_print "put: ABOR" - @sock.send(line, Socket::MSG_OOB) - resp = getmultiline - unless ["426", "226", "225"].include?(resp[0, 3]) - raise FTPProtoError, resp - end - return resp - end - - # - # Returns the status (STAT command). - # - # pathname:: when stat is invoked with pathname as a parameter it acts like - # list but a lot faster and over the same tcp session. - # - def status(pathname = nil) - line = pathname ? "STAT #{pathname}" : "STAT" - if /[\r\n]/ =~ line - raise ArgumentError, "A line must not contain CR or LF" - end - debug_print "put: #{line}" - @sock.send(line + CRLF, Socket::MSG_OOB) - return getresp - end - - # - # Returns the raw last modification time of the (remote) file in the format - # "YYYYMMDDhhmmss" (MDTM command). - # - # Use +mtime+ if you want a parsed Time instance. - # - def mdtm(filename) - resp = sendcmd("MDTM #{filename}") - if resp.start_with?("213") - return get_body(resp) - end - end - - # - # Issues the HELP command. - # - def help(arg = nil) - cmd = "HELP" - if arg - cmd = cmd + " " + arg - end - sendcmd(cmd) - end - - # - # Exits the FTP session. - # - def quit - voidcmd("QUIT") - end - - # - # Issues a NOOP command. - # - # Does nothing except return a response. - # - def noop - voidcmd("NOOP") - end - - # - # Issues a SITE command. - # - def site(arg) - cmd = "SITE " + arg - voidcmd(cmd) - end - - # - # Issues a FEAT command - # - # Returns an array of supported optional features - # - def features - resp = sendcmd("FEAT") - if !resp.start_with?("211") - raise FTPReplyError, resp - end - - feats = [] - resp.split("\n").each do |line| - next if !line.start_with?(' ') # skip status lines - - feats << line.strip - end - - return feats - end - - # - # Issues an OPTS command - # - name Should be the name of the option to set - # - params is any optional parameters to supply with the option - # - # example: option('UTF8', 'ON') => 'OPTS UTF8 ON' - # - def option(name, params = nil) - cmd = "OPTS #{name}" - cmd += " #{params}" if params - - voidcmd(cmd) - end - - # - # Closes the connection. Further operations are impossible until you open - # a new connection with #connect. - # - def close - if @sock and not @sock.closed? - begin - @sock.shutdown(Socket::SHUT_WR) rescue nil - orig, self.read_timeout = self.read_timeout, 3 - @sock.read rescue nil - ensure - @sock.close - self.read_timeout = orig - end - end - end - - # - # Returns +true+ if and only if the connection is closed. - # - def closed? - @sock == nil or @sock.closed? - end - - # handler for response code 227 - # (Entering Passive Mode (h1,h2,h3,h4,p1,p2)) - # - # Returns host and port. - def parse227(resp) # :nodoc: - if !resp.start_with?("227") - raise FTPReplyError, resp - end - if m = /(?\d+(?:,\d+){3}),(?\d+,\d+)/.match(resp) - if @use_pasv_ip - host = parse_pasv_ipv4_host(m["host"]) - else - host = @bare_sock.remote_address.ip_address - end - return host, parse_pasv_port(m["port"]) - else - raise FTPProtoError, resp - end - end - private :parse227 - - def parse_pasv_ipv4_host(s) - return s.tr(",", ".") - end - private :parse_pasv_ipv4_host - - def parse_pasv_ipv6_host(s) - return s.split(/,/).map { |i| - "%02x" % i.to_i - }.each_slice(2).map(&:join).join(":") - end - private :parse_pasv_ipv6_host - - def parse_pasv_port(s) - return s.split(/,/).map(&:to_i).inject { |x, y| - (x << 8) + y - } - end - private :parse_pasv_port - - # handler for response code 229 - # (Extended Passive Mode Entered) - # - # Returns host and port. - def parse229(resp) # :nodoc: - if !resp.start_with?("229") - raise FTPReplyError, resp - end - if m = /\((?[!-~])\k\k(?\d+)\k\)/.match(resp) - return @bare_sock.remote_address.ip_address, m["port"].to_i - else - raise FTPProtoError, resp - end - end - private :parse229 - - # handler for response code 257 - # ("PATHNAME" created) - # - # Returns host and port. - def parse257(resp) # :nodoc: - if !resp.start_with?("257") - raise FTPReplyError, resp - end - return resp.slice(/"(([^"]|"")*)"/, 1).to_s.gsub(/""/, '"') - end - private :parse257 - - # - # Writes debug message to the debug output stream - # - def debug_print(msg) - @debug_output << msg + "\n" if @debug_mode && @debug_output - end - - # :stopdoc: - class NullSocket - def read_timeout=(sec) - end - - def closed? - true - end - - def close - end - - def method_missing(mid, *args) - raise FTPConnectionError, "not connected" - end - end - - class BufferedSocket < BufferedIO - [:local_address, :remote_address, :addr, :peeraddr, :send, :shutdown].each do |method| - define_method(method) { |*args| - @io.__send__(method, *args) - } - end - - def read(len = nil) - if len - s = super(len, String.new, true) - return s.empty? ? nil : s - else - result = String.new - while s = super(DEFAULT_BLOCKSIZE, String.new, true) - break if s.empty? - result << s - end - return result - end - end - - def gets - line = readuntil("\n", true) - return line.empty? ? nil : line - end - - def readline - line = gets - if line.nil? - raise EOFError, "end of file reached" - end - return line - end - end - - if defined?(OpenSSL::SSL::SSLSocket) - class BufferedSSLSocket < BufferedSocket - def initialize(*args, **options) - super - @is_shutdown = false - end - - def shutdown(*args) - # SSL_shutdown() will be called from SSLSocket#close, and - # SSL_shutdown() will send the "close notify" alert to the peer, - # so shutdown(2) should not be called. - @is_shutdown = true - end - - def send(mesg, flags, dest = nil) - # Ignore flags and dest. - @io.write(mesg) - end - - private - - def rbuf_fill - if @is_shutdown - raise EOFError, "shutdown has been called" - else - super - end - end - end - end - # :startdoc: - end -end - - -# Documentation comments: -# - sourced from pickaxe and nutshell, with improvements (hopefully) diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/net-ftp.gemspec ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/net-ftp.gemspec --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.3/net-ftp.gemspec 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.3/net-ftp.gemspec 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Shugo Maeda"] - spec.email = ["shugo@ruby-lang.org"] - - spec.summary = %q{Support for the File Transfer Protocol.} - spec.description = %q{Support for the File Transfer Protocol.} - spec.homepage = "https://github.com/ruby/net-ftp" - spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(bin|test|spec|features)/}) } - end - spec.require_paths = ["lib"] - - spec.add_dependency "net-protocol" - spec.add_dependency "time" -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/Gemfile ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/Gemfile --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/Gemfile 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/Gemfile 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gemspec + +gem "rake" +gem "test-unit" diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/LICENSE.txt ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/LICENSE.txt --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/LICENSE.txt 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/README.md ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/README.md --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/README.md 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/README.md 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,56 @@ +# Net::FTP + +This class implements the File Transfer Protocol. If you have used a +command-line FTP program, and are familiar with the commands, you will be +able to use this class easily. Some extra features are included to take +advantage of Ruby's style and strengths. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'net-ftp' +``` + +And then execute: + + $ bundle install + +Or install it yourself as: + + $ gem install net-ftp + +## Usage + +### Example 1 + +```ruby +ftp = Net::FTP.new('example.com') +ftp.login +files = ftp.chdir('pub/lang/ruby/contrib') +files = ftp.list('n*') +ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024) +ftp.close +``` + +### Example 2 + +```ruby +Net::FTP.open('example.com') do |ftp| + ftp.login + files = ftp.chdir('pub/lang/ruby/contrib') + files = ftp.list('n*') + ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024) +end +``` + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-ftp. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/Rakefile ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/Rakefile --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/Rakefile 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/Rakefile 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,10 @@ +require "bundler/gem_tasks" +require "rake/testtask" + +Rake::TestTask.new(:test) do |t| + t.libs << "test/lib" + t.ruby_opts << "-rhelper" + t.test_files = FileList["test/**/test_*.rb"] +end + +task :default => :test diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/lib/net/ftp.rb ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/lib/net/ftp.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/lib/net/ftp.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/lib/net/ftp.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,1556 @@ +# frozen_string_literal: true +# +# = net/ftp.rb - FTP Client Library +# +# Written by Shugo Maeda . +# +# Documentation by Gavin Sinclair, sourced from "Programming Ruby" (Hunt/Thomas) +# and "Ruby In a Nutshell" (Matsumoto), used with permission. +# +# This library is distributed under the terms of the Ruby license. +# You can freely distribute/modify this library. +# +# It is included in the Ruby standard library. +# +# See the Net::FTP class for an overview. +# + +require "socket" +require "monitor" +require "net/protocol" +require "time" +begin + require "openssl" +rescue LoadError +end + +module Net + + # :stopdoc: + class FTPError < StandardError; end + class FTPReplyError < FTPError; end + class FTPTempError < FTPError; end + class FTPPermError < FTPError; end + class FTPProtoError < FTPError; end + class FTPConnectionError < FTPError; end + # :startdoc: + + # + # This class implements the File Transfer Protocol. If you have used a + # command-line FTP program, and are familiar with the commands, you will be + # able to use this class easily. Some extra features are included to take + # advantage of Ruby's style and strengths. + # + # == Example + # + # require 'net/ftp' + # + # === Example 1 + # + # ftp = Net::FTP.new('example.com') + # ftp.login + # files = ftp.chdir('pub/lang/ruby/contrib') + # files = ftp.list('n*') + # ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024) + # ftp.close + # + # === Example 2 + # + # Net::FTP.open('example.com') do |ftp| + # ftp.login + # files = ftp.chdir('pub/lang/ruby/contrib') + # files = ftp.list('n*') + # ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024) + # end + # + # == Major Methods + # + # The following are the methods most likely to be useful to users: + # - FTP.open + # - #getbinaryfile + # - #gettextfile + # - #putbinaryfile + # - #puttextfile + # - #chdir + # - #nlst + # - #size + # - #rename + # - #delete + # + class FTP < Protocol + include MonitorMixin + if defined?(OpenSSL::SSL) + include OpenSSL + include SSL + end + + # :stopdoc: + VERSION = "0.3.4" + FTP_PORT = 21 + CRLF = "\r\n" + DEFAULT_BLOCKSIZE = BufferedIO::BUFSIZE + @@default_passive = true + # :startdoc: + + # When +true+, transfers are performed in binary mode. Default: +true+. + attr_reader :binary + + # When +true+, the connection is in passive mode. Default: +true+. + attr_accessor :passive + + # When +true+, use the IP address in PASV responses. Otherwise, it uses + # the same IP address for the control connection. Default: +false+. + attr_accessor :use_pasv_ip + + # When +true+, all traffic to and from the server is written + # to +$stdout+. Default: +false+. + attr_accessor :debug_mode + + # Sets or retrieves the output stream for debugging. + # Output stream will be used only when +debug_mode+ is set to true. + # The default value is +$stdout+. + attr_accessor :debug_output + + # Sets or retrieves the +resume+ status, which decides whether incomplete + # transfers are resumed or restarted. Default: +false+. + attr_accessor :resume + + # Number of seconds to wait for the connection to open. Any number + # may be used, including Floats for fractional seconds. If the FTP + # object cannot open a connection in this many seconds, it raises a + # Net::OpenTimeout exception. The default value is +nil+. + attr_accessor :open_timeout + + # Number of seconds to wait for the TLS handshake. Any number + # may be used, including Floats for fractional seconds. If the FTP + # object cannot complete the TLS handshake in this many seconds, it + # raises a Net::OpenTimeout exception. The default value is +nil+. + # If +ssl_handshake_timeout+ is +nil+, +open_timeout+ is used instead. + attr_accessor :ssl_handshake_timeout + + # Number of seconds to wait for one block to be read (via one read(2) + # call). Any number may be used, including Floats for fractional + # seconds. If the FTP object cannot read data in this many seconds, + # it raises a Timeout::Error exception. The default value is 60 seconds. + attr_reader :read_timeout + + # Setter for the read_timeout attribute. + def read_timeout=(sec) + @sock.read_timeout = sec + @read_timeout = sec + end + + # The server's welcome message. + attr_reader :welcome + + # The server's last response code. + attr_reader :last_response_code + alias lastresp last_response_code + + # The server's last response. + attr_reader :last_response + + # When +true+, connections are in passive mode per default. + # Default: +true+. + def self.default_passive=(value) + @@default_passive = value + end + + # When +true+, connections are in passive mode per default. + # Default: +true+. + def self.default_passive + @@default_passive + end + + # + # A synonym for FTP.new, but with a mandatory host parameter. + # + # If a block is given, it is passed the +FTP+ object, which will be closed + # when the block finishes, or when an exception is raised. + # + def FTP.open(host, *args) + if block_given? + ftp = new(host, *args) + begin + yield ftp + ensure + ftp.close + end + else + new(host, *args) + end + end + + # :call-seq: + # Net::FTP.new(host = nil, options = {}) + # + # Creates and returns a new +FTP+ object. If a +host+ is given, a connection + # is made. + # + # +options+ is an option hash, each key of which is a symbol. + # + # The available options are: + # + # port:: Port number (default value is 21) + # ssl:: If +options+[:ssl] is true, then an attempt will be made + # to use SSL (now TLS) to connect to the server. For this + # to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] + # extensions need to be installed. If +options+[:ssl] is a + # hash, it's passed to OpenSSL::SSL::SSLContext#set_params + # as parameters. + # private_data_connection:: If true, TLS is used for data connections. + # Default: +true+ when +options+[:ssl] is true. + # implicit_ftps:: If true, TLS is established on initial connection. + # Default: +false+ + # username:: Username for login. If +options+[:username] is the string + # "anonymous" and the +options+[:password] is +nil+, + # "anonymous@" is used as a password. + # password:: Password for login. + # account:: Account information for ACCT. + # passive:: When +true+, the connection is in passive mode. Default: + # +true+. + # open_timeout:: Number of seconds to wait for the connection to open. + # See Net::FTP#open_timeout for details. Default: +nil+. + # read_timeout:: Number of seconds to wait for one block to be read. + # See Net::FTP#read_timeout for details. Default: +60+. + # ssl_handshake_timeout:: Number of seconds to wait for the TLS + # handshake. + # See Net::FTP#ssl_handshake_timeout for + # details. Default: +nil+. + # use_pasv_ip:: When +true+, use the IP address in PASV responses. + # Otherwise, it uses the same IP address for the control + # connection. Default: +false+. + # debug_mode:: When +true+, all traffic to and from the server is + # written to +$stdout+. Default: +false+. + # + def initialize(host = nil, user_or_options = {}, passwd = nil, acct = nil) + super() + begin + options = user_or_options.to_hash + rescue NoMethodError + # for backward compatibility + options = {} + options[:username] = user_or_options + options[:password] = passwd + options[:account] = acct + end + @host = nil + if options[:ssl] + unless defined?(OpenSSL::SSL) + raise "SSL extension not installed" + end + ssl_params = options[:ssl] == true ? {} : options[:ssl] + @ssl_context = SSLContext.new + @ssl_context.set_params(ssl_params) + if defined?(VerifyCallbackProc) + @ssl_context.verify_callback = VerifyCallbackProc + end + @ssl_context.session_cache_mode = + OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | + OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE + @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess } + @ssl_session = nil + if options[:private_data_connection].nil? + @private_data_connection = true + else + @private_data_connection = options[:private_data_connection] + end + if options[:implicit_ftps].nil? + @implicit_ftps = false + else + @implicit_ftps = options[:implicit_ftps] + end + else + @ssl_context = nil + if options[:private_data_connection] + raise ArgumentError, + "private_data_connection can be set to true only when ssl is enabled" + end + @private_data_connection = false + if options[:implicit_ftps] + raise ArgumentError, + "implicit_ftps can be set to true only when ssl is enabled" + end + @implicit_ftps = false + end + @binary = true + if options[:passive].nil? + @passive = @@default_passive + else + @passive = options[:passive] + end + if options[:debug_mode].nil? + @debug_mode = false + else + @debug_mode = options[:debug_mode] + end + @debug_output = $stdout + @resume = false + @bare_sock = @sock = NullSocket.new + @logged_in = false + @open_timeout = options[:open_timeout] + @ssl_handshake_timeout = options[:ssl_handshake_timeout] + @read_timeout = options[:read_timeout] || 60 + @use_pasv_ip = options[:use_pasv_ip] || false + if host + connect(host, options[:port] || FTP_PORT) + if options[:username] + login(options[:username], options[:password], options[:account]) + end + end + end + + # A setter to toggle transfers in binary mode. + # +newmode+ is either +true+ or +false+ + def binary=(newmode) + if newmode != @binary + @binary = newmode + send_type_command if @logged_in + end + end + + # Sends a command to destination host, with the current binary sendmode + # type. + # + # If binary mode is +true+, then "TYPE I" (image) is sent, otherwise "TYPE + # A" (ascii) is sent. + def send_type_command # :nodoc: + if @binary + voidcmd("TYPE I") + else + voidcmd("TYPE A") + end + end + private :send_type_command + + # Toggles transfers in binary mode and yields to a block. + # This preserves your current binary send mode, but allows a temporary + # transaction with binary sendmode of +newmode+. + # + # +newmode+ is either +true+ or +false+ + def with_binary(newmode) # :nodoc: + oldmode = binary + self.binary = newmode + begin + yield + ensure + self.binary = oldmode + end + end + private :with_binary + + # Obsolete + def return_code # :nodoc: + warn("Net::FTP#return_code is obsolete and do nothing", uplevel: 1) + return "\n" + end + + # Obsolete + def return_code=(s) # :nodoc: + warn("Net::FTP#return_code= is obsolete and do nothing", uplevel: 1) + end + + # Constructs a socket with +host+ and +port+. + # + # If SOCKSSocket is defined and the environment (ENV) defines + # SOCKS_SERVER, then a SOCKSSocket is returned, else a Socket is + # returned. + def open_socket(host, port) # :nodoc: + return Timeout.timeout(@open_timeout, OpenTimeout) { + if defined? SOCKSSocket and ENV["SOCKS_SERVER"] + @passive = true + SOCKSSocket.open(host, port) + else + Socket.tcp(host, port) + end + } + end + private :open_socket + + def start_tls_session(sock) + ssl_sock = SSLSocket.new(sock, @ssl_context) + ssl_sock.sync_close = true + ssl_sock.hostname = @host if ssl_sock.respond_to? :hostname= + if @ssl_session && + Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout + # ProFTPD returns 425 for data connections if session is not reused. + ssl_sock.session = @ssl_session + end + ssl_socket_connect(ssl_sock, @ssl_handshake_timeout || @open_timeout) + if @ssl_context.verify_mode != VERIFY_NONE + ssl_sock.post_connection_check(@host) + end + return ssl_sock + end + private :start_tls_session + + # + # Establishes an FTP connection to host, optionally overriding the default + # port. If the environment variable +SOCKS_SERVER+ is set, sets up the + # connection through a SOCKS proxy. Raises an exception (typically + # Errno::ECONNREFUSED) if the connection cannot be established. + # + def connect(host, port = FTP_PORT) + debug_print "connect: #{host}:#{port}" + synchronize do + @host = host + @bare_sock = open_socket(host, port) + if @ssl_context + begin + unless @implicit_ftps + set_socket(BufferedSocket.new(@bare_sock, read_timeout: @read_timeout)) + voidcmd("AUTH TLS") + end + set_socket(BufferedSSLSocket.new(start_tls_session(@bare_sock), read_timeout: @read_timeout), @implicit_ftps) + if @private_data_connection + voidcmd("PBSZ 0") + voidcmd("PROT P") + end + rescue OpenSSL::SSL::SSLError, OpenTimeout + @sock.close + raise + end + else + set_socket(BufferedSocket.new(@bare_sock, read_timeout: @read_timeout)) + end + end + end + + # + # Set the socket used to connect to the FTP server. + # + # May raise FTPReplyError if +get_greeting+ is false. + def set_socket(sock, get_greeting = true) + synchronize do + @sock = sock + if get_greeting + voidresp + end + end + end + + # If string +s+ includes the PASS command (password), then the contents of + # the password are cleaned from the string using "*" + def sanitize(s) # :nodoc: + if s =~ /^PASS /i + return s[0, 5] + "*" * (s.length - 5) + else + return s + end + end + private :sanitize + + # Ensures that +line+ has a control return / line feed (CRLF) and writes + # it to the socket. + def putline(line) # :nodoc: + debug_print "put: #{sanitize(line)}" + if /[\r\n]/ =~ line + raise ArgumentError, "A line must not contain CR or LF" + end + line = line + CRLF + @sock.write(line) + end + private :putline + + # Reads a line from the sock. If EOF, then it will raise EOFError + def getline # :nodoc: + line = @sock.readline # if get EOF, raise EOFError + line.sub!(/(\r\n|\n|\r)\z/n, "") + debug_print "get: #{sanitize(line)}" + return line + end + private :getline + + # Receive a section of lines until the response code's match. + def getmultiline # :nodoc: + lines = [] + lines << getline + code = lines.last.slice(/\A([0-9a-zA-Z]{3})-/, 1) + if code + delimiter = code + " " + begin + lines << getline + end until lines.last.start_with?(delimiter) + end + return lines.join("\n") + "\n" + end + private :getmultiline + + # Receives a response from the destination host. + # + # Returns the response code or raises FTPTempError, FTPPermError, or + # FTPProtoError + def getresp # :nodoc: + @last_response = getmultiline + @last_response_code = @last_response[0, 3] + case @last_response_code + when /\A[123]/ + return @last_response + when /\A4/ + raise FTPTempError, @last_response + when /\A5/ + raise FTPPermError, @last_response + else + raise FTPProtoError, @last_response + end + end + private :getresp + + # Receives a response. + # + # Raises FTPReplyError if the first position of the response code is not + # equal 2. + def voidresp # :nodoc: + resp = getresp + if !resp.start_with?("2") + raise FTPReplyError, resp + end + end + private :voidresp + + # + # Sends a command and returns the response. + # + def sendcmd(cmd) + synchronize do + putline(cmd) + return getresp + end + end + + # + # Sends a command and expect a response beginning with '2'. + # + def voidcmd(cmd) + synchronize do + putline(cmd) + voidresp + end + end + + # Constructs and send the appropriate PORT (or EPRT) command + def sendport(host, port) # :nodoc: + remote_address = @bare_sock.remote_address + if remote_address.ipv4? + cmd = "PORT " + (host.split(".") + port.divmod(256)).join(",") + elsif remote_address.ipv6? + cmd = sprintf("EPRT |2|%s|%d|", host, port) + else + raise FTPProtoError, host + end + voidcmd(cmd) + end + private :sendport + + # Constructs a TCPServer socket + def makeport # :nodoc: + Addrinfo.tcp(@bare_sock.local_address.ip_address, 0).listen + end + private :makeport + + # sends the appropriate command to enable a passive connection + def makepasv # :nodoc: + if @bare_sock.remote_address.ipv4? + host, port = parse227(sendcmd("PASV")) + else + host, port = parse229(sendcmd("EPSV")) + end + return host, port + end + private :makepasv + + # Constructs a connection for transferring data + def transfercmd(cmd, rest_offset = nil) # :nodoc: + if @passive + host, port = makepasv + succeeded = false + begin + conn = open_socket(host, port) + if @resume and rest_offset + resp = sendcmd("REST " + rest_offset.to_s) + if !resp.start_with?("3") + raise FTPReplyError, resp + end + end + resp = sendcmd(cmd) + # skip 2XX for some ftp servers + resp = getresp if resp.start_with?("2") + if !resp.start_with?("1") + raise FTPReplyError, resp + end + succeeded = true + ensure + conn&.close if !succeeded + end + else + sock = makeport + begin + addr = sock.local_address + sendport(addr.ip_address, addr.ip_port) + if @resume and rest_offset + resp = sendcmd("REST " + rest_offset.to_s) + if !resp.start_with?("3") + raise FTPReplyError, resp + end + end + resp = sendcmd(cmd) + # skip 2XX for some ftp servers + resp = getresp if resp.start_with?("2") + if !resp.start_with?("1") + raise FTPReplyError, resp + end + conn, = sock.accept + sock.shutdown(Socket::SHUT_WR) rescue nil + sock.read rescue nil + ensure + sock.close + end + end + if @private_data_connection + return BufferedSSLSocket.new(start_tls_session(conn), + read_timeout: @read_timeout) + else + return BufferedSocket.new(conn, read_timeout: @read_timeout) + end + end + private :transfercmd + + # + # Logs in to the remote host. The session must have been + # previously connected. If +user+ is the string "anonymous" and + # the +password+ is +nil+, "anonymous@" is used as a password. If + # the +acct+ parameter is not +nil+, an FTP ACCT command is sent + # following the successful login. Raises an exception on error + # (typically Net::FTPPermError). + # + def login(user = "anonymous", passwd = nil, acct = nil) + if user == "anonymous" and passwd == nil + passwd = "anonymous@" + end + + resp = "" + synchronize do + resp = sendcmd('USER ' + user) + if resp.start_with?("3") + raise FTPReplyError, resp if passwd.nil? + resp = sendcmd('PASS ' + passwd) + end + if resp.start_with?("3") + raise FTPReplyError, resp if acct.nil? + resp = sendcmd('ACCT ' + acct) + end + end + if !resp.start_with?("2") + raise FTPReplyError, resp + end + @welcome = resp + send_type_command + @logged_in = true + end + + # + # Puts the connection into binary (image) mode, issues the given command, + # and fetches the data returned, passing it to the associated block in + # chunks of +blocksize+ characters. Note that +cmd+ is a server command + # (such as "RETR myfile"). + # + def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data + synchronize do + with_binary(true) do + begin + conn = transfercmd(cmd, rest_offset) + while data = conn.read(blocksize) + yield(data) + end + conn.shutdown(Socket::SHUT_WR) rescue nil + conn.read_timeout = 1 + conn.read rescue nil + ensure + conn.close if conn + end + voidresp + end + end + end + + # + # Puts the connection into ASCII (text) mode, issues the given command, and + # passes the resulting data, one line at a time, to the associated block. If + # no block is given, prints the lines. Note that +cmd+ is a server command + # (such as "RETR myfile"). + # + def retrlines(cmd) # :yield: line + synchronize do + with_binary(false) do + begin + conn = transfercmd(cmd) + while line = conn.gets + yield(line.sub(/\r?\n\z/, ""), !line.match(/\n\z/).nil?) + end + conn.shutdown(Socket::SHUT_WR) rescue nil + conn.read_timeout = 1 + conn.read rescue nil + ensure + conn.close if conn + end + voidresp + end + end + end + + # + # Puts the connection into binary (image) mode, issues the given server-side + # command (such as "STOR myfile"), and sends the contents of the file named + # +file+ to the server. If the optional block is given, it also passes it + # the data, in chunks of +blocksize+ characters. + # + def storbinary(cmd, file, blocksize, rest_offset = nil) # :yield: data + if rest_offset + file.seek(rest_offset, IO::SEEK_SET) + end + synchronize do + with_binary(true) do + begin + conn = transfercmd(cmd) + while buf = file.read(blocksize) + conn.write(buf) + yield(buf) if block_given? + end + conn.shutdown(Socket::SHUT_WR) rescue nil + conn.read_timeout = 1 + conn.read rescue nil + ensure + conn.close if conn + end + voidresp + end + end + rescue Errno::EPIPE + # EPIPE, in this case, means that the data connection was unexpectedly + # terminated. Rather than just raising EPIPE to the caller, check the + # response on the control connection. If getresp doesn't raise a more + # appropriate exception, re-raise the original exception. + getresp + raise + end + + # + # Puts the connection into ASCII (text) mode, issues the given server-side + # command (such as "STOR myfile"), and sends the contents of the file + # named +file+ to the server, one line at a time. If the optional block is + # given, it also passes it the lines. + # + def storlines(cmd, file) # :yield: line + synchronize do + with_binary(false) do + begin + conn = transfercmd(cmd) + while buf = file.gets + if buf[-2, 2] != CRLF + buf = buf.chomp + CRLF + end + conn.write(buf) + yield(buf) if block_given? + end + conn.shutdown(Socket::SHUT_WR) rescue nil + conn.read_timeout = 1 + conn.read rescue nil + ensure + conn.close if conn + end + voidresp + end + end + rescue Errno::EPIPE + # EPIPE, in this case, means that the data connection was unexpectedly + # terminated. Rather than just raising EPIPE to the caller, check the + # response on the control connection. If getresp doesn't raise a more + # appropriate exception, re-raise the original exception. + getresp + raise + end + + # + # Retrieves +remotefile+ in binary mode, storing the result in +localfile+. + # If +localfile+ is nil, returns retrieved data. + # If a block is supplied, it is passed the retrieved data in +blocksize+ + # chunks. + # + def getbinaryfile(remotefile, localfile = File.basename(remotefile), + blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data + f = nil + result = nil + if localfile + if @resume + rest_offset = File.size?(localfile) + f = File.open(localfile, "a") + else + rest_offset = nil + f = File.open(localfile, "w") + end + elsif !block_given? + result = String.new + end + begin + f&.binmode + retrbinary("RETR #{remotefile}", blocksize, rest_offset) do |data| + f&.write(data) + block&.(data) + result&.concat(data) + end + return result + ensure + f&.close + end + end + + # + # Retrieves +remotefile+ in ASCII (text) mode, storing the result in + # +localfile+. + # If +localfile+ is nil, returns retrieved data. + # If a block is supplied, it is passed the retrieved data one + # line at a time. + # + def gettextfile(remotefile, localfile = File.basename(remotefile), + &block) # :yield: line + f = nil + result = nil + if localfile + f = File.open(localfile, "w") + elsif !block_given? + result = String.new + end + begin + retrlines("RETR #{remotefile}") do |line, newline| + l = newline ? line + "\n" : line + f&.print(l) + block&.(line, newline) + result&.concat(l) + end + return result + ensure + f&.close + end + end + + # + # Retrieves +remotefile+ in whatever mode the session is set (text or + # binary). See #gettextfile and #getbinaryfile. + # + def get(remotefile, localfile = File.basename(remotefile), + blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data + if @binary + getbinaryfile(remotefile, localfile, blocksize, &block) + else + gettextfile(remotefile, localfile, &block) + end + end + + # + # Transfers +localfile+ to the server in binary mode, storing the result in + # +remotefile+. If a block is supplied, calls it, passing in the transmitted + # data in +blocksize+ chunks. + # + def putbinaryfile(localfile, remotefile = File.basename(localfile), + blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data + if @resume + begin + rest_offset = size(remotefile) + rescue Net::FTPPermError + rest_offset = nil + end + else + rest_offset = nil + end + f = File.open(localfile) + begin + f.binmode + if rest_offset + storbinary("APPE #{remotefile}", f, blocksize, rest_offset, &block) + else + storbinary("STOR #{remotefile}", f, blocksize, rest_offset, &block) + end + ensure + f.close + end + end + + # + # Transfers +localfile+ to the server in ASCII (text) mode, storing the result + # in +remotefile+. If callback or an associated block is supplied, calls it, + # passing in the transmitted data one line at a time. + # + def puttextfile(localfile, remotefile = File.basename(localfile), &block) # :yield: line + f = File.open(localfile) + begin + storlines("STOR #{remotefile}", f, &block) + ensure + f.close + end + end + + # + # Transfers +localfile+ to the server in whatever mode the session is set + # (text or binary). See #puttextfile and #putbinaryfile. + # + def put(localfile, remotefile = File.basename(localfile), + blocksize = DEFAULT_BLOCKSIZE, &block) + if @binary + putbinaryfile(localfile, remotefile, blocksize, &block) + else + puttextfile(localfile, remotefile, &block) + end + end + + # + # Sends the ACCT command. + # + # This is a less common FTP command, to send account + # information if the destination host requires it. + # + def acct(account) + cmd = "ACCT " + account + voidcmd(cmd) + end + + # + # Returns an array of filenames in the remote directory. + # + def nlst(dir = nil) + cmd = "NLST" + if dir + cmd = "#{cmd} #{dir}" + end + files = [] + retrlines(cmd) do |line| + files.push(line) + end + return files + end + + # + # Returns an array of file information in the directory (the output is like + # `ls -l`). If a block is given, it iterates through the listing. + # + def list(*args, &block) # :yield: line + cmd = "LIST" + args.each do |arg| + cmd = "#{cmd} #{arg}" + end + lines = [] + retrlines(cmd) do |line| + lines << line + end + if block + lines.each(&block) + end + return lines + end + alias ls list + alias dir list + + # + # MLSxEntry represents an entry in responses of MLST/MLSD. + # Each entry has the facts (e.g., size, last modification time, etc.) + # and the pathname. + # + class MLSxEntry + attr_reader :facts, :pathname + + def initialize(facts, pathname) + @facts = facts + @pathname = pathname + end + + standard_facts = %w(size modify create type unique perm + lang media-type charset) + standard_facts.each do |factname| + define_method factname.gsub(/-/, "_") do + facts[factname] + end + end + + # + # Returns +true+ if the entry is a file (i.e., the value of the type + # fact is file). + # + def file? + return facts["type"] == "file" + end + + # + # Returns +true+ if the entry is a directory (i.e., the value of the + # type fact is dir, cdir, or pdir). + # + def directory? + if /\A[cp]?dir\z/.match(facts["type"]) + return true + else + return false + end + end + + # + # Returns +true+ if the APPE command may be applied to the file. + # + def appendable? + return facts["perm"].include?(?a) + end + + # + # Returns +true+ if files may be created in the directory by STOU, + # STOR, APPE, and RNTO. + # + def creatable? + return facts["perm"].include?(?c) + end + + # + # Returns +true+ if the file or directory may be deleted by DELE/RMD. + # + def deletable? + return facts["perm"].include?(?d) + end + + # + # Returns +true+ if the directory may be entered by CWD/CDUP. + # + def enterable? + return facts["perm"].include?(?e) + end + + # + # Returns +true+ if the file or directory may be renamed by RNFR. + # + def renamable? + return facts["perm"].include?(?f) + end + + # + # Returns +true+ if the listing commands, LIST, NLST, and MLSD are + # applied to the directory. + # + def listable? + return facts["perm"].include?(?l) + end + + # + # Returns +true+ if the MKD command may be used to create a new + # directory within the directory. + # + def directory_makable? + return facts["perm"].include?(?m) + end + + # + # Returns +true+ if the objects in the directory may be deleted, or + # the directory may be purged. + # + def purgeable? + return facts["perm"].include?(?p) + end + + # + # Returns +true+ if the RETR command may be applied to the file. + # + def readable? + return facts["perm"].include?(?r) + end + + # + # Returns +true+ if the STOR command may be applied to the file. + # + def writable? + return facts["perm"].include?(?w) + end + end + + CASE_DEPENDENT_PARSER = ->(value) { value } + CASE_INDEPENDENT_PARSER = ->(value) { value.downcase } + DECIMAL_PARSER = ->(value) { value.to_i } + OCTAL_PARSER = ->(value) { value.to_i(8) } + TIME_PARSER = ->(value, local = false) { + unless /\A(?\d{4})(?\d{2})(?\d{2}) + (?\d{2})(?\d{2})(?\d{2}) + (?:\.(?\d{1,17}))?/x =~ value + value = value[0, 97] + "..." if value.size > 100 + raise FTPProtoError, "invalid time-val: #{value}" + end + usec = ".#{fractions}".to_r * 1_000_000 if fractions + Time.public_send(local ? :local : :utc, year, month, day, hour, min, sec, usec) + } + FACT_PARSERS = Hash.new(CASE_DEPENDENT_PARSER) + FACT_PARSERS["size"] = DECIMAL_PARSER + FACT_PARSERS["modify"] = TIME_PARSER + FACT_PARSERS["create"] = TIME_PARSER + FACT_PARSERS["type"] = CASE_INDEPENDENT_PARSER + FACT_PARSERS["unique"] = CASE_DEPENDENT_PARSER + FACT_PARSERS["perm"] = CASE_INDEPENDENT_PARSER + FACT_PARSERS["lang"] = CASE_INDEPENDENT_PARSER + FACT_PARSERS["media-type"] = CASE_INDEPENDENT_PARSER + FACT_PARSERS["charset"] = CASE_INDEPENDENT_PARSER + FACT_PARSERS["unix.mode"] = OCTAL_PARSER + FACT_PARSERS["unix.owner"] = DECIMAL_PARSER + FACT_PARSERS["unix.group"] = DECIMAL_PARSER + FACT_PARSERS["unix.ctime"] = TIME_PARSER + FACT_PARSERS["unix.atime"] = TIME_PARSER + + def parse_mlsx_entry(entry) + facts, pathname = entry.chomp.split(/ /, 2) + unless pathname + raise FTPProtoError, entry + end + return MLSxEntry.new( + facts.scan(/(.*?)=(.*?);/).each_with_object({}) { + |(factname, value), h| + name = factname.downcase + h[name] = FACT_PARSERS[name].(value) + }, + pathname) + end + private :parse_mlsx_entry + + # + # Returns data (e.g., size, last modification time, entry type, etc.) + # about the file or directory specified by +pathname+. + # If +pathname+ is omitted, the current directory is assumed. + # + def mlst(pathname = nil) + cmd = pathname ? "MLST #{pathname}" : "MLST" + resp = sendcmd(cmd) + if !resp.start_with?("250") + raise FTPReplyError, resp + end + line = resp.lines[1] + unless line + raise FTPProtoError, resp + end + entry = line.sub(/\A(250-| *)/, "") + return parse_mlsx_entry(entry) + end + + # + # Returns an array of the entries of the directory specified by + # +pathname+. + # Each entry has the facts (e.g., size, last modification time, etc.) + # and the pathname. + # If a block is given, it iterates through the listing. + # If +pathname+ is omitted, the current directory is assumed. + # + def mlsd(pathname = nil, &block) # :yield: entry + cmd = pathname ? "MLSD #{pathname}" : "MLSD" + entries = [] + retrlines(cmd) do |line| + entries << parse_mlsx_entry(line) + end + if block + entries.each(&block) + end + return entries + end + + # + # Renames a file on the server. + # + def rename(fromname, toname) + resp = sendcmd("RNFR #{fromname}") + if !resp.start_with?("3") + raise FTPReplyError, resp + end + voidcmd("RNTO #{toname}") + end + + # + # Deletes a file on the server. + # + def delete(filename) + resp = sendcmd("DELE #{filename}") + if resp.start_with?("250") + return + elsif resp.start_with?("5") + raise FTPPermError, resp + else + raise FTPReplyError, resp + end + end + + # + # Changes the (remote) directory. + # + def chdir(dirname) + if dirname == ".." + begin + voidcmd("CDUP") + return + rescue FTPPermError => e + if e.message[0, 3] != "500" + raise e + end + end + end + cmd = "CWD #{dirname}" + voidcmd(cmd) + end + + def get_body(resp) # :nodoc: + resp.slice(/\A[0-9a-zA-Z]{3} (.*)$/, 1) + end + private :get_body + + # + # Returns the size of the given (remote) filename. + # + def size(filename) + with_binary(true) do + resp = sendcmd("SIZE #{filename}") + if !resp.start_with?("213") + raise FTPReplyError, resp + end + return get_body(resp).to_i + end + end + + # + # Returns the last modification time of the (remote) file. If +local+ is + # +true+, it is returned as a local time, otherwise it's a UTC time. + # + def mtime(filename, local = false) + return TIME_PARSER.(mdtm(filename), local) + end + + # + # Creates a remote directory. + # + def mkdir(dirname) + resp = sendcmd("MKD #{dirname}") + return parse257(resp) + end + + # + # Removes a remote directory. + # + def rmdir(dirname) + voidcmd("RMD #{dirname}") + end + + # + # Returns the current remote directory. + # + def pwd + resp = sendcmd("PWD") + return parse257(resp) + end + alias getdir pwd + + # + # Returns system information. + # + def system + resp = sendcmd("SYST") + if !resp.start_with?("215") + raise FTPReplyError, resp + end + return get_body(resp) + end + + # + # Aborts the previous command (ABOR command). + # + def abort + line = "ABOR" + CRLF + debug_print "put: ABOR" + @sock.send(line, Socket::MSG_OOB) + resp = getmultiline + unless ["426", "226", "225"].include?(resp[0, 3]) + raise FTPProtoError, resp + end + return resp + end + + # + # Returns the status (STAT command). + # + # pathname:: when stat is invoked with pathname as a parameter it acts like + # list but a lot faster and over the same tcp session. + # + def status(pathname = nil) + line = pathname ? "STAT #{pathname}" : "STAT" + if /[\r\n]/ =~ line + raise ArgumentError, "A line must not contain CR or LF" + end + debug_print "put: #{line}" + @sock.send(line + CRLF, Socket::MSG_OOB) + return getresp + end + + # + # Returns the raw last modification time of the (remote) file in the format + # "YYYYMMDDhhmmss" (MDTM command). + # + # Use +mtime+ if you want a parsed Time instance. + # + def mdtm(filename) + resp = sendcmd("MDTM #{filename}") + if resp.start_with?("213") + return get_body(resp) + end + end + + # + # Issues the HELP command. + # + def help(arg = nil) + cmd = "HELP" + if arg + cmd = cmd + " " + arg + end + sendcmd(cmd) + end + + # + # Exits the FTP session. + # + def quit + voidcmd("QUIT") + end + + # + # Issues a NOOP command. + # + # Does nothing except return a response. + # + def noop + voidcmd("NOOP") + end + + # + # Issues a SITE command. + # + def site(arg) + cmd = "SITE " + arg + voidcmd(cmd) + end + + # + # Issues a FEAT command + # + # Returns an array of supported optional features + # + def features + resp = sendcmd("FEAT") + if !resp.start_with?("211") + raise FTPReplyError, resp + end + + feats = [] + resp.split("\n").each do |line| + next if !line.start_with?(' ') # skip status lines + + feats << line.strip + end + + return feats + end + + # + # Issues an OPTS command + # - name Should be the name of the option to set + # - params is any optional parameters to supply with the option + # + # example: option('UTF8', 'ON') => 'OPTS UTF8 ON' + # + def option(name, params = nil) + cmd = "OPTS #{name}" + cmd += " #{params}" if params + + voidcmd(cmd) + end + + # + # Closes the connection. Further operations are impossible until you open + # a new connection with #connect. + # + def close + if @sock and not @sock.closed? + begin + @sock.shutdown(Socket::SHUT_WR) rescue nil + orig, self.read_timeout = self.read_timeout, 3 + @sock.read rescue nil + ensure + @sock.close + self.read_timeout = orig + end + end + end + + # + # Returns +true+ if and only if the connection is closed. + # + def closed? + @sock == nil or @sock.closed? + end + + # handler for response code 227 + # (Entering Passive Mode (h1,h2,h3,h4,p1,p2)) + # + # Returns host and port. + def parse227(resp) # :nodoc: + if !resp.start_with?("227") + raise FTPReplyError, resp + end + if m = /(?\d+(?:,\d+){3}),(?\d+,\d+)/.match(resp) + if @use_pasv_ip + host = parse_pasv_ipv4_host(m["host"]) + else + host = @bare_sock.remote_address.ip_address + end + return host, parse_pasv_port(m["port"]) + else + raise FTPProtoError, resp + end + end + private :parse227 + + def parse_pasv_ipv4_host(s) + return s.tr(",", ".") + end + private :parse_pasv_ipv4_host + + def parse_pasv_ipv6_host(s) + return s.split(/,/).map { |i| + "%02x" % i.to_i + }.each_slice(2).map(&:join).join(":") + end + private :parse_pasv_ipv6_host + + def parse_pasv_port(s) + return s.split(/,/).map(&:to_i).inject { |x, y| + (x << 8) + y + } + end + private :parse_pasv_port + + # handler for response code 229 + # (Extended Passive Mode Entered) + # + # Returns host and port. + def parse229(resp) # :nodoc: + if !resp.start_with?("229") + raise FTPReplyError, resp + end + if m = /\((?[!-~])\k\k(?\d+)\k\)/.match(resp) + return @bare_sock.remote_address.ip_address, m["port"].to_i + else + raise FTPProtoError, resp + end + end + private :parse229 + + # handler for response code 257 + # ("PATHNAME" created) + # + # Returns host and port. + def parse257(resp) # :nodoc: + if !resp.start_with?("257") + raise FTPReplyError, resp + end + return resp.slice(/"(([^"]|"")*)"/, 1).to_s.gsub(/""/, '"') + end + private :parse257 + + # + # Writes debug message to the debug output stream + # + def debug_print(msg) + @debug_output << msg + "\n" if @debug_mode && @debug_output + end + + # :stopdoc: + class NullSocket + def read_timeout=(sec) + end + + def closed? + true + end + + def close + end + + def method_missing(mid, *args) + raise FTPConnectionError, "not connected" + end + end + + class BufferedSocket < BufferedIO + [:local_address, :remote_address, :addr, :peeraddr, :send, :shutdown].each do |method| + define_method(method) { |*args| + @io.__send__(method, *args) + } + end + + def read(len = nil) + if len + s = super(len, String.new, true) + return s.empty? ? nil : s + else + result = String.new + while s = super(DEFAULT_BLOCKSIZE, String.new, true) + break if s.empty? + result << s + end + return result + end + end + + def gets + line = readuntil("\n", true) + return line.empty? ? nil : line + end + + def readline + line = gets + if line.nil? + raise EOFError, "end of file reached" + end + return line + end + end + + if defined?(OpenSSL::SSL::SSLSocket) + class BufferedSSLSocket < BufferedSocket + def initialize(*args, **options) + super + @is_shutdown = false + end + + def shutdown(*args) + # SSL_shutdown() will be called from SSLSocket#close, and + # SSL_shutdown() will send the "close notify" alert to the peer, + # so shutdown(2) should not be called. + @is_shutdown = true + end + + def send(mesg, flags, dest = nil) + # Ignore flags and dest. + @io.write(mesg) + end + + private + + def rbuf_fill + if @is_shutdown + raise EOFError, "shutdown has been called" + else + super + end + end + end + end + # :startdoc: + end +end + + +# Documentation comments: +# - sourced from pickaxe and nutshell, with improvements (hopefully) diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/net-ftp.gemspec ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/net-ftp.gemspec --- ruby3.3-3.3.0focal3/.bundle/gems/net-ftp-0.3.4/net-ftp.gemspec 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-ftp-0.3.4/net-ftp.gemspec 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +name = File.basename(__FILE__, ".gemspec") +version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir| + break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| + /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 + end rescue nil +end + +Gem::Specification.new do |spec| + spec.name = name + spec.version = version + spec.authors = ["Shugo Maeda"] + spec.email = ["shugo@ruby-lang.org"] + + spec.summary = %q{Support for the File Transfer Protocol.} + spec.description = %q{Support for the File Transfer Protocol.} + spec.homepage = "https://github.com/ruby/net-ftp" + spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") + spec.licenses = ["Ruby", "BSD-2-Clause"] + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do + `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(bin|test|spec|features)/}) } + end + spec.require_paths = ["lib"] + + spec.add_dependency "net-protocol" + spec.add_dependency "time" +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/Gemfile ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/Gemfile --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/Gemfile 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/Gemfile 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gemspec - -gem "rake" -gem "rdoc" -gem "test-unit" -gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core" - -gem "benchmark-driver" diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/LICENSE.txt ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/LICENSE.txt --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/LICENSE.txt 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,84 +0,0 @@ -Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. - -------------------------------------------------------------------------- - -This software includes documentation which has been copied from the relevant -RFCs. The copied documentation is covered by the following licenses: - -RFC 3501 (Editor: M. Crispin) -Full Copyright Statement - - Copyright (C) The Internet Society (2003). All Rights Reserved. - - This document and translations of it may be copied and furnished to - others, and derivative works that comment on or otherwise explain it - or assist in its implementation may be prepared, copied, published - and distributed, in whole or in part, without restriction of any - kind, provided that the above copyright notice and this paragraph are - included on all such copies and derivative works. However, this - document itself may not be modified in any way, such as by removing - the copyright notice or references to the Internet Society or other - Internet organizations, except as needed for the purpose of - developing Internet standards in which case the procedures for - copyrights defined in the Internet Standards process must be - followed, or as required to translate it into languages other than - English. - - The limited permissions granted above are perpetual and will not be - revoked by the Internet Society or its successors or assigns. v This - document and the information contained herein is provided on an "AS - IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK - FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT - LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL - NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY - OR FITNESS FOR A PARTICULAR PURPOSE. - - -RFC9051 (Editors: A. Melnikov, B. Leiba) -Copyright Notice - - Copyright (c) 2021 IETF Trust and the persons identified as the - document authors. All rights reserved. - - This document is subject to BCP 78 and the IETF Trust's Legal - Provisions Relating to IETF Documents - (https://trustee.ietf.org/license-info) in effect on the date of - publication of this document. Please review these documents - carefully, as they describe your rights and restrictions with respect - to this document. Code Components extracted from this document must - include Simplified BSD License text as described in Section 4.e of - the Trust Legal Provisions and are provided without warranty as - described in the Simplified BSD License. - - This document may contain material from IETF Documents or IETF - Contributions published or made publicly available before November - 10, 2008. The person(s) controlling the copyright in some of this - material may not have granted the IETF Trust the right to allow - modifications of such material outside the IETF Standards Process. - Without obtaining an adequate license from the person(s) controlling - the copyright in such materials, this document may not be modified - outside the IETF Standards Process, and derivative works of it may - not be created outside the IETF Standards Process, except to format - it for publication as an RFC or to translate it into languages other - than English. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/README.md ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/README.md --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/README.md 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -# Net::IMAP - -Net::IMAP implements Internet Message Access Protocol (IMAP) client -functionality. The protocol is described in [IMAP](https://tools.ietf.org/html/rfc3501). - -## Installation - -Add this line to your application's Gemfile: - -```ruby -gem 'net-imap' -``` - -And then execute: - - $ bundle install - -Or install it yourself as: - - $ gem install net-imap - -## Usage - -### Connect with TLS to port 993 - -```ruby -imap = Net::IMAP.new('mail.example.com', ssl: true) -imap.port => 993 -imap.tls_verified? => true -case imap.greeting.name -in /OK/i - # The client is connected in the "Not Authenticated" state. - imap.authenticate("PLAIN", "joe_user", "joes_password") -in /PREAUTH/i - # The client is connected in the "Authenticated" state. -end -``` - -### List sender and subject of all recent messages in the default mailbox - -```ruby -imap.examine('INBOX') -imap.search(["RECENT"]).each do |message_id| - envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] - puts "#{envelope.from[0].name}: \t#{envelope.subject}" -end -``` - -### Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03" - -```ruby -imap.select('Mail/sent-mail') -if not imap.list('Mail/', 'sent-apr03') - imap.create('Mail/sent-apr03') -end -imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id| - imap.copy(message_id, "Mail/sent-apr03") - imap.store(message_id, "+FLAGS", [:Deleted]) -end -imap.expunge -``` - -## Development - -After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. - -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). - -## Contributing - -Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-imap. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/Rakefile ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/Rakefile --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/Rakefile 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/Rakefile 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require "bundler/gem_tasks" -require "rake/testtask" -require "rake/clean" - -Rake::TestTask.new(:test) do |t| - t.libs << "test/lib" - t.ruby_opts << "-rhelper" - t.test_files = FileList["test/**/test_*.rb"] -end - -task :default => :test diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/docs/styles.css ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/docs/styles.css --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/docs/styles.css 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/docs/styles.css 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -/* this is a work in progress. :) */ - -main .method-header { - background: rgba(27,31,35,0.05); - border: 1px solid #6C8C22; - padding: 0.5em; - border-radius: 4px; - /* padding: 0 0.5em; */ - /* border-width: 0 1px; */ - /* border-color: #6C8C22; */ - /* border-style: solid; */ -} - -main .method-description, main .aliases { - padding-left: 1em; -} - -body { - /* - * The default (300) can be too low contrast. Also, many fonts don't - * distinguish between 300->400, so ... had no effect. - */ - font-weight: 400; -} diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/authenticators.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/authenticators.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/authenticators.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/authenticators.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -# Backward compatible delegators from Net::IMAP to Net::IMAP::SASL. -module Net::IMAP::Authenticators - - # Deprecated. Use Net::IMAP::SASL.add_authenticator instead. - def add_authenticator(...) - warn( - "%s.%s is deprecated. Use %s.%s instead." % [ - Net::IMAP, __method__, Net::IMAP::SASL, __method__ - ], - uplevel: 1 - ) - Net::IMAP::SASL.add_authenticator(...) - end - - # Deprecated. Use Net::IMAP::SASL.authenticator instead. - def authenticator(...) - warn( - "%s.%s is deprecated. Use %s.%s instead." % [ - Net::IMAP, __method__, Net::IMAP::SASL, __method__ - ], - uplevel: 1 - ) - Net::IMAP::SASL.authenticator(...) - end - - Net::IMAP.extend self -end - -class Net::IMAP - PlainAuthenticator = SASL::PlainAuthenticator # :nodoc: - deprecate_constant :PlainAuthenticator - - XOauth2Authenticator = SASL::XOAuth2Authenticator # :nodoc: - deprecate_constant :XOauth2Authenticator -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/command_data.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/command_data.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/command_data.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/command_data.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,307 +0,0 @@ -# frozen_string_literal: true - -require "date" - -require_relative "errors" - -module Net - class IMAP < Protocol - - private - - def validate_data(data) - case data - when nil - when String - when Integer - NumValidator.ensure_number(data) - when Array - if data[0] == 'CHANGEDSINCE' - NumValidator.ensure_mod_sequence_value(data[1]) - else - data.each do |i| - validate_data(i) - end - end - when Time, Date, DateTime - when Symbol - else - data.validate - end - end - - def send_data(data, tag = nil) - case data - when nil - put_string("NIL") - when String - send_string_data(data, tag) - when Integer - send_number_data(data) - when Array - send_list_data(data, tag) - when Date - send_date_data(data) - when Time, DateTime - send_time_data(data) - when Symbol - send_symbol_data(data) - else - data.send_data(self, tag) - end - end - - def send_string_data(str, tag = nil) - if str.empty? - put_string('""') - elsif str.match?(/[\r\n]/n) - # literal, because multiline - send_literal(str, tag) - elsif !str.ascii_only? - if @utf8_strings - # quoted string - send_quoted_string(str) - else - # literal, because of non-ASCII bytes - send_literal(str, tag) - end - elsif str.match?(/[(){ \x00-\x1f\x7f%*"\\]/n) - # quoted string - send_quoted_string(str) - else - put_string(str) - end - end - - def send_quoted_string(str) - put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"') - end - - def send_literal(str, tag = nil) - synchronize do - put_string("{" + str.bytesize.to_s + "}" + CRLF) - @continued_command_tag = tag - @continuation_request_exception = nil - begin - @continuation_request_arrival.wait - e = @continuation_request_exception || @exception - raise e if e - put_string(str) - ensure - @continued_command_tag = nil - @continuation_request_exception = nil - end - end - end - - def send_number_data(num) - put_string(num.to_s) - end - - def send_list_data(list, tag = nil) - put_string("(") - first = true - list.each do |i| - if first - first = false - else - put_string(" ") - end - send_data(i, tag) - end - put_string(")") - end - - def send_date_data(date) put_string Net::IMAP.encode_date(date) end - def send_time_data(time) put_string Net::IMAP.encode_time(time) end - - def send_symbol_data(symbol) - put_string("\\" + symbol.to_s) - end - - class RawData # :nodoc: - def send_data(imap, tag) - imap.__send__(:put_string, @data) - end - - def validate - end - - private - - def initialize(data) - @data = data - end - end - - class Atom # :nodoc: - def send_data(imap, tag) - imap.__send__(:put_string, @data) - end - - def validate - end - - private - - def initialize(data) - @data = data - end - end - - class QuotedString # :nodoc: - def send_data(imap, tag) - imap.__send__(:send_quoted_string, @data) - end - - def validate - end - - private - - def initialize(data) - @data = data - end - end - - class Literal # :nodoc: - def send_data(imap, tag) - imap.__send__(:send_literal, @data, tag) - end - - def validate - end - - private - - def initialize(data) - @data = data - end - end - - class MessageSet # :nodoc: - def send_data(imap, tag) - imap.__send__(:put_string, format_internal(@data)) - end - - def validate - validate_internal(@data) - end - - private - - def initialize(data) - @data = data - end - - def format_internal(data) - case data - when "*" - return data - when Integer - if data == -1 - return "*" - else - return data.to_s - end - when Range - return format_internal(data.first) + - ":" + format_internal(data.last) - when Array - return data.collect {|i| format_internal(i)}.join(",") - when ThreadMember - return data.seqno.to_s + - ":" + data.children.collect {|i| format_internal(i).join(",")} - end - end - - def validate_internal(data) - case data - when "*" - when Integer - NumValidator.ensure_nz_number(data) - when Range - when Array - data.each do |i| - validate_internal(i) - end - when ThreadMember - data.children.each do |i| - validate_internal(i) - end - else - raise DataFormatError, data.inspect - end - end - end - - class ClientID # :nodoc: - - def send_data(imap, tag) - imap.__send__(:send_data, format_internal(@data), tag) - end - - def validate - validate_internal(@data) - end - - private - - def initialize(data) - @data = data - end - - def validate_internal(client_id) - client_id.to_h.each do |k,v| - unless StringFormatter.valid_string?(k) - raise DataFormatError, client_id.inspect - end - end - rescue NoMethodError, TypeError # to_h failed - raise DataFormatError, client_id.inspect - end - - def format_internal(client_id) - return nil if client_id.nil? - client_id.to_h.flat_map {|k,v| - [StringFormatter.string(k), StringFormatter.nstring(v)] - } - end - - end - - module StringFormatter - - LITERAL_REGEX = /[\x80-\xff\r\n]/n - - module_function - - # Allows symbols in addition to strings - def valid_string?(str) - str.is_a?(Symbol) || str.respond_to?(:to_str) - end - - # Allows nil, symbols, and strings - def valid_nstring?(str) - str.nil? || valid_string?(str) - end - - # coerces using +to_s+ - def string(str) - str = str.to_s - if str =~ LITERAL_REGEX - Literal.new(str) - else - QuotedString.new(str) - end - end - - # coerces non-nil using +to_s+ - def nstring(str) - str.nil? ? nil : string(str) - end - - end - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/data_encoding.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/data_encoding.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/data_encoding.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/data_encoding.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,214 +0,0 @@ -# frozen_string_literal: true - -require "date" -require "time" - -require_relative "errors" - -module Net - class IMAP < Protocol - - # strftime/strptime format for an IMAP4 +date+, excluding optional dquotes. - # Use via the encode_date and decode_date methods. - # - # date = date-text / DQUOTE date-text DQUOTE - # date-text = date-day "-" date-month "-" date-year - # - # date-day = 1*2DIGIT - # ; Day of month - # date-month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / - # "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" - # date-year = 4DIGIT - STRFDATE = "%d-%b-%Y" - - # strftime/strptime format for an IMAP4 +date-time+, including dquotes. - # See the encode_datetime and decode_datetime methods. - # - # date-time = DQUOTE date-day-fixed "-" date-month "-" date-year - # SP time SP zone DQUOTE - # - # date-day-fixed = (SP DIGIT) / 2DIGIT - # ; Fixed-format version of date-day - # date-month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / - # "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" - # date-year = 4DIGIT - # time = 2DIGIT ":" 2DIGIT ":" 2DIGIT - # ; Hours minutes seconds - # zone = ("+" / "-") 4DIGIT - # ; Signed four-digit value of hhmm representing - # ; hours and minutes east of Greenwich (that is, - # ; the amount that the given time differs from - # ; Universal Time). Subtracting the timezone - # ; from the given time will give the UT form. - # ; The Universal Time zone is "+0000". - # - # Note that Time.strptime "%d" flexibly parses either space or zero - # padding. However, the DQUOTEs are *not* optional. - STRFTIME = '"%d-%b-%Y %H:%M:%S %z"' - - # Decode a string from modified UTF-7 format to UTF-8. - # - # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a - # slightly modified version of this to encode mailbox names - # containing non-ASCII characters; see [IMAP] section 5.1.3. - # - # Net::IMAP does _not_ automatically encode and decode - # mailbox names to and from UTF-7. - def self.decode_utf7(s) - return s.gsub(/&([A-Za-z0-9+,]+)?-/n) { - if base64 = $1 - (base64.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE) - else - "&" - end - } - end - - # Encode a string from UTF-8 format to modified UTF-7. - def self.encode_utf7(s) - return s.gsub(/(&)|[^\x20-\x7e]+/) { - if $1 - "&-" - else - base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0") - "&" + base64.delete("=").tr("/", ",") + "-" - end - }.force_encoding("ASCII-8BIT") - end - - # Formats +time+ as an IMAP4 date. - def self.encode_date(date) - date.to_date.strftime STRFDATE - end - - # :call-seq: decode_date(string) -> Date - # - # Decodes +string+ as an IMAP formatted "date". - # - # Double quotes are optional. Day of month may be padded with zero or - # space. See STRFDATE. - def self.decode_date(string) - string = string.delete_prefix('"').delete_suffix('"') - Date.strptime(string, STRFDATE) - end - - # :call-seq: encode_datetime(time) -> string - # - # Formats +time+ as an IMAP4 date-time. - def self.encode_datetime(time) - time.to_datetime.strftime STRFTIME - end - - # :call-seq: decode_datetime(string) -> DateTime - # - # Decodes +string+ as an IMAP4 formatted "date-time". - # - # NOTE: Although double-quotes are not optional in the IMAP grammar, - # Net::IMAP currently parses "date-time" values as "quoted" strings and this - # removes the quotation marks. To be useful for strings which have already - # been parsed as a quoted string, this method makes double-quotes optional. - # - # See STRFTIME. - def self.decode_datetime(string) - unless string.start_with?(?") && string.end_with?(?") - string = '"%s"' % [string] - end - DateTime.strptime(string, STRFTIME) - end - - # :call-seq: decode_time(string) -> Time - # - # Decodes +string+ as an IMAP4 formatted "date-time". - # - # Same as +decode_datetime+, but returning a Time instead. - def self.decode_time(string) - unless string.start_with?(?") && string.end_with?(?") - string = '"%s"' % [string] - end - Time.strptime(string, STRFTIME) - end - - class << self - alias encode_time encode_datetime - alias format_date encode_date - alias format_time encode_time - alias parse_date decode_date - alias parse_datetime decode_datetime - alias parse_time decode_time - - # alias format_datetime encode_datetime # n.b. this is overridden below... - end - - # DEPRECATED:: The original version returned incorrectly formatted strings. - # Strings returned by encode_datetime or format_time use the - # correct IMAP4rev1 syntax for "date-time". - # - # This invalid format has been temporarily retained for backward - # compatibility. A future release will change this method to return the - # correct format. - def self.format_datetime(time) - warn("#{self}.format_datetime incorrectly formats IMAP date-time. " \ - "Convert to #{self}.encode_datetime or #{self}.format_time instead.", - uplevel: 1, category: :deprecated) - time.strftime("%d-%b-%Y %H:%M %z") - end - - # Common validators of number and nz_number types - module NumValidator # :nodoc - module_function - - # Check is passed argument valid 'number' in RFC 3501 terminology - def valid_number?(num) - # [RFC 3501] - # number = 1*DIGIT - # ; Unsigned 32-bit integer - # ; (0 <= n < 4,294,967,296) - num >= 0 && num < 4294967296 - end - - # Check is passed argument valid 'nz_number' in RFC 3501 terminology - def valid_nz_number?(num) - # [RFC 3501] - # nz-number = digit-nz *DIGIT - # ; Non-zero unsigned 32-bit integer - # ; (0 < n < 4,294,967,296) - num != 0 && valid_number?(num) - end - - # Check is passed argument valid 'mod_sequence_value' in RFC 4551 terminology - def valid_mod_sequence_value?(num) - # mod-sequence-value = 1*DIGIT - # ; Positive unsigned 64-bit integer - # ; (mod-sequence) - # ; (1 <= n < 18,446,744,073,709,551,615) - num >= 1 && num < 18446744073709551615 - end - - # Ensure argument is 'number' or raise DataFormatError - def ensure_number(num) - return if valid_number?(num) - - msg = "number must be unsigned 32-bit integer: #{num}" - raise DataFormatError, msg - end - - # Ensure argument is 'nz_number' or raise DataFormatError - def ensure_nz_number(num) - return if valid_nz_number?(num) - - msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}" - raise DataFormatError, msg - end - - # Ensure argument is 'mod_sequence_value' or raise DataFormatError - def ensure_mod_sequence_value(num) - return if valid_mod_sequence_value?(num) - - msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}" - raise DataFormatError, msg - end - - end - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/deprecated_client_options.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/deprecated_client_options.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/deprecated_client_options.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/deprecated_client_options.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,139 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - - # This module handles deprecated arguments to various Net::IMAP methods. - module DeprecatedClientOptions - - # :call-seq: - # Net::IMAP.new(host, **options) # standard keyword options - # Net::IMAP.new(host, options) # obsolete hash options - # Net::IMAP.new(host, port) # obsolete port argument - # Net::IMAP.new(host, port, usessl, certs = nil, verify = true) # deprecated SSL arguments - # - # Translates Net::IMAP.new arguments for backward compatibility. - # - # ==== Obsolete arguments - # - # Using obsolete arguments does not a warning. Obsolete arguments will be - # deprecated by a future release. - # - # If a second positional argument is given and it is a hash (or is - # convertable via +#to_hash+), it is converted to keyword arguments. - # - # # Obsolete: - # Net::IMAP.new("imap.example.com", options_hash) - # # Use instead: - # Net::IMAP.new("imap.example.com", **options_hash) - # - # If a second positional argument is given and it is not a hash, it is - # converted to the +port+ keyword argument. - # # Obsolete: - # Net::IMAP.new("imap.example.com", 114433) - # # Use instead: - # Net::IMAP.new("imap.example.com", port: 114433) - # - # ==== Deprecated arguments - # - # Using deprecated arguments prints a warning. Convert to keyword - # arguments to avoid the warning. Deprecated arguments will be removed in - # a future release. - # - # If +usessl+ is false, +certs+, and +verify+ are ignored. When it true, - # all three arguments are converted to the +ssl+ keyword argument. - # Without +certs+ or +verify+, it is converted to ssl: true. - # # DEPRECATED: - # Net::IMAP.new("imap.example.com", nil, true) # => prints a warning - # # Use instead: - # Net::IMAP.new("imap.example.com", ssl: true) - # - # When +certs+ is a path to a directory, it is converted to ca_path: - # certs. - # # DEPRECATED: - # Net::IMAP.new("imap.example.com", nil, true, "/path/to/certs") # => prints a warning - # # Use instead: - # Net::IMAP.new("imap.example.com", ssl: {ca_path: "/path/to/certs"}) - # - # When +certs+ is a path to a file, it is converted to ca_file: - # certs. - # # DEPRECATED: - # Net::IMAP.new("imap.example.com", nil, true, "/path/to/cert.pem") # => prints a warning - # # Use instead: - # Net::IMAP.new("imap.example.com", ssl: {ca_file: "/path/to/cert.pem"}) - # - # When +verify+ is +false+, it is converted to verify_mode: - # OpenSSL::SSL::VERIFY_NONE. - # # DEPRECATED: - # Net::IMAP.new("imap.example.com", nil, true, nil, false) # => prints a warning - # # Use instead: - # Net::IMAP.new("imap.example.com", ssl: {verify_mode: OpenSSL::SSL::VERIFY_NONE}) - # - def initialize(host, port_or_options = nil, *deprecated, **options) - if port_or_options.nil? && deprecated.empty? - super host, **options - elsif options.any? - # Net::IMAP.new(host, *__invalid__, **options) - raise ArgumentError, "Do not combine deprecated and keyword arguments" - elsif port_or_options.respond_to?(:to_hash) and deprecated.any? - # Net::IMAP.new(host, options, *__invalid__) - raise ArgumentError, "Do not use deprecated SSL params with options hash" - elsif port_or_options.respond_to?(:to_hash) - super host, **Hash.try_convert(port_or_options) - elsif deprecated.empty? - super host, port: port_or_options - elsif deprecated.shift - warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1 - super host, port: port_or_options, ssl: create_ssl_params(*deprecated) - else - warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1 - super host, port: port_or_options, ssl: false - end - end - - # :call-seq: - # starttls(**options) # standard - # starttls(options = {}) # obsolete - # starttls(certs = nil, verify = true) # deprecated - # - # Translates Net::IMAP#starttls arguments for backward compatibility. - # - # Support for +certs+ and +verify+ will be dropped in a future release. - # - # See ::new for interpretation of +certs+ and +verify+. - def starttls(*deprecated, **options) - if deprecated.empty? - super(**options) - elsif options.any? - # starttls(*__invalid__, **options) - raise ArgumentError, "Do not combine deprecated and keyword options" - elsif deprecated.first.respond_to?(:to_hash) && deprecated.length > 1 - # starttls(*__invalid__, **options) - raise ArgumentError, "Do not use deprecated verify param with options hash" - elsif deprecated.first.respond_to?(:to_hash) - super(**Hash.try_convert(deprecated.first)) - else - warn "DEPRECATED: Call Net::IMAP#starttls with keyword options", uplevel: 1 - super(**create_ssl_params(*deprecated)) - end - end - - private - - def create_ssl_params(certs = nil, verify = true) - params = {} - if certs - if File.file?(certs) - params[:ca_file] = certs - elsif File.directory?(certs) - params[:ca_path] = certs - end - end - params[:verify_mode] = - verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE - params - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/errors.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/errors.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/errors.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/errors.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - - # Superclass of IMAP errors. - class Error < StandardError - end - - # Error raised when data is in the incorrect format. - class DataFormatError < Error - end - - # Error raised when a response from the server is non-parseable. - class ResponseParseError < Error - end - - # Superclass of all errors used to encapsulate "fail" responses - # from the server. - class ResponseError < Error - - # The response that caused this error - attr_accessor :response - - def initialize(response) - @response = response - - super @response.data.text - end - - end - - # Error raised upon a "NO" response from the server, indicating - # that the client command could not be completed successfully. - class NoResponseError < ResponseError - end - - # Error raised upon a "BAD" response from the server, indicating - # that the client command violated the IMAP protocol, or an internal - # server failure has occurred. - class BadResponseError < ResponseError - end - - # Error raised upon a "BYE" response from the server, indicating - # that the client is not being allowed to login, or has been timed - # out due to inactivity. - class ByeResponseError < ResponseError - end - - # Error raised when the server sends an invalid response. - # - # This is different from UnknownResponseError: the response has been - # rejected. Although it may be parsable, the server is forbidden from - # sending it in the current context. The client should automatically - # disconnect, abruptly (without logout). - # - # Note that InvalidResponseError does not inherit from ResponseError: it - # can be raised before the response is fully parsed. A related - # ResponseParseError or ResponseError may be the #cause. - class InvalidResponseError < Error - end - - # Error raised upon an unknown response from the server. - # - # This is different from InvalidResponseError: the response may be a - # valid extension response and the server may be allowed to send it in - # this context, but Net::IMAP either does not know how to parse it or - # how to handle it. This could result from enabling unknown or - # unhandled extensions. The connection may still be usable, - # but—depending on context—it may be prudent to disconnect. - class UnknownResponseError < ResponseError - end - - RESPONSE_ERRORS = Hash.new(ResponseError) # :nodoc: - RESPONSE_ERRORS["NO"] = NoResponseError - RESPONSE_ERRORS["BAD"] = BadResponseError - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/fetch_data.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/fetch_data.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/fetch_data.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/fetch_data.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,518 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - - # Net::IMAP::FetchData represents the contents of a FETCH response. - # Net::IMAP#fetch and Net::IMAP#uid_fetch both return an array of - # FetchData objects. - # - # === Fetch attributes - # - # See {[IMAP4rev1 §7.4.2]}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] - # and {[IMAP4rev2 §7.5.2]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2] - # for a full description of the standard fetch response data items, and - # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. - # - # ==== Static fetch data items - # - # Most message attributes are static, and must never change for a given - # (server, account, mailbox, UIDVALIDITY, UID) tuple. - # - # The static fetch data items defined by both - # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and - # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] are: - # - # * "UID" --- See #uid. - # * "BODY" --- See #body. - # * "BODY[#{section_spec}]", - # "BODY[#{section_spec}]<#{offset}>" --- See #message, - # #part, #header, #header_fields, #header_fields_not, #mime, and #text. - # * "BODYSTRUCTURE" --- See #bodystructure. - # * "ENVELOPE" --- See #envelope. - # * "INTERNALDATE" --- See #internaldate. - # * "RFC822.SIZE" --- See #rfc822_size. - # - # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] adds the - # additional fetch items from the +BINARY+ extension - # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html]: - # - # * "BINARY[#{part}]", - # "BINARY[#{part}]<#{offset}>" -- See #binary. - # * "BINARY.SIZE[#{part}]" -- See #binary_size. - # - # Several static message attributes in - # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] are obsolete and - # been removed from - # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]: - # - # * "RFC822" --- See #rfc822 or replace with - # "BODY[]" and #message. - # * "RFC822.HEADER" --- See #rfc822_header or replace with - # "BODY[HEADER]" and #header. - # * "RFC822.TEXT" --- See #rfc822_text or replace with - # "BODY[TEXT]" and #text. - # - # Net::IMAP supports static attributes defined by the following extensions: - # * +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html] - # * "EMAILID" --- See #emailid. - # * "THREADID" --- See #threadid. - # - # * +X-GM-EXT-1+ {[non-standard Gmail - # extension]}[https://developers.google.com/gmail/imap/imap-extensions] - # * "X-GM-MSGID" --- unique message ID. Access via #attr. - # * "X-GM-THRID" --- Thread ID. Access via #attr. - # - # [Note:] - # >>> - # Additional static fields are defined in other \IMAP extensions, but - # Net::IMAP can't parse them yet. - # - # ==== Dynamic message attributes - # - # Some message attributes can be dynamically changed, for example using the - # {STORE command}[rdoc-ref:Net::IMAP#store]. - # - # The only dynamic message attribute defined by - # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and - # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] is: - # - # * "FLAGS" --- See #flags. - # - # Net::IMAP supports dynamic attributes defined by the following extensions: - # - # * +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]: - # * "MODSEQ" --- See #modseq. - # * +X-GM-EXT-1+ {[non-standard Gmail - # extension]}[https://developers.google.com/gmail/imap/imap-extensions] - # * "X-GM-LABELS" --- Gmail labels. Access via #attr. - # - # [Note:] - # >>> - # Additional dynamic fields are defined in other \IMAP extensions, but - # Net::IMAP can't parse them yet. - # - # === Implicitly setting \Seen and using +PEEK+ - # - # Unless the mailbox is has been opened as read-only, fetching - # BODY[#{section}] or BINARY[#{section}] - # will implicitly set the \Seen flag. To avoid this, fetch using - # BODY.PEEK[#{section}] or BINARY.PEEK[#{section}] - # instead. - # - # Note that the data will always be _returned_ without ".PEEK", in - # BODY[#{specifier}] or BINARY[#{section}]. - # - class FetchData < Struct.new(:seqno, :attr) - ## - # method: seqno - # :call-seq: seqno -> Integer - # - # The message sequence number. - # - # [Note] - # This is never the unique identifier (UID), not even for the - # Net::IMAP#uid_fetch result. The UID is available from #uid, if it was - # returned. - - ## - # method: attr - # :call-seq: attr -> hash - # - # Each key specifies a message attribute, and the value is the - # corresponding data item. Standard data items have corresponding - # accessor methods. The definitions of each attribute type is documented - # on its accessor. - # - # >>> - # *Note:* #seqno is not a message attribute. - - # :call-seq: attr_upcase -> hash - # - # A transformation of #attr, with all the keys converted to upper case. - # - # Header field names are case-preserved but not case-sensitive, so this is - # used by #header_fields and #header_fields_not. - def attr_upcase; attr.transform_keys(&:upcase) end - - # :call-seq: - # body -> body structure or nil - # - # Returns an alternate form of #bodystructure, without any extension data. - # - # This is the same as getting the value for "BODY" from #attr. - # - # [Note] - # Use #message, #part, #header, #header_fields, #header_fields_not, - # #text, or #mime to retrieve BODY[#{section_spec}] attributes. - def body; attr["BODY"] end - - # :call-seq: - # message(offset: bytes) -> string or nil - # - # The RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html] - # expression of the entire message, as a string. - # - # See #part for a description of +offset+. - # - # RFC5322 messages can be parsed using the "mail" gem. - # - # This is the same as getting the value for "BODY[]" or - # "BODY[]<#{offset}>" from #attr. - # - # See also: #header, #text, and #mime. - def message(offset: nil) attr[body_section_attr(offset: offset)] end - - # :call-seq: - # part(*part_nums, offset: bytes) -> string or nil - # - # The string representation of a particular MIME part. - # - # +part_nums+ forms a path of MIME part numbers, counting up from +1+, - # which may specify an arbitrarily nested part, similarly to Array#dig. - # Messages that don't use MIME, or MIME messages that are not multipart - # and don't hold an encapsulated message, only have part +1+. - # - # If a zero-based +offset+ is given, the returned string is a substring of - # the entire contents, starting at that origin octet. This means that - # BODY[]<0> MAY be truncated, but BODY[] is never - # truncated. - # - # This is the same as getting the value of - # "BODY[#{part_nums.join(".")}]" or - # "BODY[#{part_nums.join(".")}]<#{offset}>" from #attr. - # - # See also: #message, #header, #text, and #mime. - def part(index, *subparts, offset: nil) - attr[body_section_attr([index, *subparts], offset: offset)] - end - - # :call-seq: - # header(*part_nums, offset: nil) -> string or nil - # header(*part_nums, fields: names, offset: nil) -> string or nil - # header(*part_nums, except: names, offset: nil) -> string or nil - # - # The {[RFC5322]}[https://www.rfc-editor.org/rfc/rfc5322.html] header of a - # message or of an encapsulated - # {[MIME-IMT]}[https://www.rfc-editor.org/rfc/rfc2046.html] - # MESSAGE/RFC822 or MESSAGE/GLOBAL message. - # - # Headers can be parsed using the "mail" gem. - # - # See #part for a description of +part_nums+ and +offset+. - # - # ==== Without +fields+ or +except+ - # This is the same as getting the value from #attr for one of: - # * BODY[HEADER] - # * BODY[HEADER]<#{offset}> - # * BODY[#{part_nums.join "."}.HEADER]" - # * BODY[#{part_nums.join "."}.HEADER]<#{offset}>" - # - # ==== With +fields+ - # When +fields+ is sent, returns a subset of the header which contains - # only the header fields that match one of the names in the list. - # - # This is the same as getting the value from #attr_upcase for one of: - # * BODY[HEADER.FIELDS (#{names.join " "})] - # * BODY[HEADER.FIELDS (#{names.join " "})]<#{offset}> - # * BODY[#{part_nums.join "."}.HEADER.FIELDS (#{names.join " "})] - # * BODY[#{part_nums.join "."}.HEADER.FIELDS (#{names.join " "})]<#{offset}> - # - # See also: #header_fields - # - # ==== With +except+ - # When +except+ is sent, returns a subset of the header which contains - # only the header fields that do _not_ match one of the names in the list. - # - # This is the same as getting the value from #attr_upcase for one of: - # * BODY[HEADER.FIELDS.NOT (#{names.join " "})] - # * BODY[HEADER.FIELDS.NOT (#{names.join " "})]<#{offset}> - # * BODY[#{part_nums.join "."}.HEADER.FIELDS.NOT (#{names.join " "})] - # * BODY[#{part_nums.join "."}.HEADER.FIELDS.NOT (#{names.join " "})]<#{offset}> - # - # See also: #header_fields_not - def header(*part_nums, fields: nil, except: nil, offset: nil) - fields && except and - raise ArgumentError, "conflicting 'fields' and 'except' arguments" - if fields - text = "HEADER.FIELDS (%s)" % [fields.join(" ").upcase] - attr_upcase[body_section_attr(part_nums, text, offset: offset)] - elsif except - text = "HEADER.FIELDS.NOT (%s)" % [except.join(" ").upcase] - attr_upcase[body_section_attr(part_nums, text, offset: offset)] - else - attr[body_section_attr(part_nums, "HEADER", offset: offset)] - end - end - - # :call-seq: - # header_fields(*names, part: [], offset: nil) -> string or nil - # - # The result from #header when called with fields: names. - def header_fields(first, *rest, part: [], offset: nil) - header(*part, fields: [first, *rest], offset: offset) - end - - # :call-seq: - # header_fields_not(*names, part: [], offset: nil) -> string or nil - # - # The result from #header when called with except: names. - def header_fields_not(first, *rest, part: [], offset: nil) - header(*part, except: [first, *rest], offset: offset) - end - - # :call-seq: - # mime(*part_nums) -> string or nil - # mime(*part_nums, offset: bytes) -> string or nil - # - # The {[MIME-IMB]}[https://www.rfc-editor.org/rfc/rfc2045.html] header for - # a message part, if it was fetched. - # - # See #part for a description of +part_nums+ and +offset+. - # - # This is the same as getting the value for - # "BODY[#{part_nums}.MIME]" or - # "BODY[#{part_nums}.MIME]<#{offset}>" from #attr. - # - # See also: #message, #header, and #text. - def mime(part, *subparts, offset: nil) - attr[body_section_attr([part, *subparts], "MIME", offset: offset)] - end - - # :call-seq: - # text(*part_nums) -> string or nil - # text(*part_nums, offset: bytes) -> string or nil - # - # The text body of a message or a message part, if it was fetched, - # omitting the {[RFC5322]}[https://www.rfc-editor.org/rfc/rfc5322.html] - # header. - # - # See #part for a description of +part_nums+ and +offset+. - # - # This is the same as getting the value from #attr for one of: - # * "BODY[TEXT]", - # * "BODY[TEXT]<#{offset}>", - # * "BODY[#{section}.TEXT]", or - # * "BODY[#{section}.TEXT]<#{offset}>". - # - # See also: #message, #header, and #mime. - def text(*part, offset: nil) - attr[body_section_attr(part, "TEXT", offset: offset)] - end - - # :call-seq: - # bodystructure -> BodyStructure struct or nil - # - # A BodyStructure object that describes the message, if it was fetched. - # - # This is the same as getting the value for "BODYSTRUCTURE" from - # #attr. - def bodystructure; attr["BODYSTRUCTURE"] end - alias body_structure bodystructure - - # :call-seq: envelope -> Envelope or nil - # - # An Envelope object that describes the envelope structure of a message. - # See the documentation for Envelope for a description of the envelope - # structure attributes. - # - # This is the same as getting the value for "ENVELOPE" from - # #attr. - def envelope; attr["ENVELOPE"] end - - # :call-seq: flags -> array of Symbols and Strings - # - # A array of flags that are set for this message. System flags are - # symbols that have been capitalized by String#capitalize. Keyword flags - # are strings and their case is not changed. - # - # This is the same as getting the value for "FLAGS" from #attr. - # - # [Note] - # The +FLAGS+ field is dynamic, and can change for a uniquely identified - # message. - def flags; attr["FLAGS"] end - - # :call-seq: internaldate -> Time or nil - # - # The internal date and time of the message on the server. This is not - # the date and time in the [RFC5322[https://tools.ietf.org/html/rfc5322]] - # header, but rather a date and time which reflects when the message was - # received. - # - # This is similar to getting the value for "INTERNALDATE" from - # #attr. - # - # [Note] - # attr["INTERNALDATE"] returns a string, and this method - # returns a Time object. - def internaldate - attr["INTERNALDATE"]&.then { IMAP.decode_time _1 } - end - alias internal_date internaldate - - # :call-seq: rfc822 -> String - # - # Semantically equivalent to #message with no arguments. - # - # This is the same as getting the value for "RFC822" from #attr. - # - # [Note] - # +IMAP4rev2+ deprecates RFC822. - def rfc822; attr["RFC822"] end - - # :call-seq: rfc822_size -> Integer - # - # A number expressing the [RFC5322[https://tools.ietf.org/html/rfc5322]] - # size of the message. - # - # This is the same as getting the value for "RFC822.SIZE" from - # #attr. - # - # [Note] - # \IMAP was originally developed for the older - # RFC822[https://www.rfc-editor.org/rfc/rfc822.html] standard, and as a - # consequence several fetch items in \IMAP incorporate "RFC822" in their - # name. With the exception of +RFC822.SIZE+, there are more modern - # replacements; for example, the modern version of +RFC822.HEADER+ is - # BODY.PEEK[HEADER]. In all cases, "RFC822" should be - # interpreted as a reference to the updated - # RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html] standard. - def rfc822_size; attr["RFC822.SIZE"] end - alias size rfc822_size - - # :call-seq: rfc822_header -> String - # - # Semantically equivalent to #header, with no arguments. - # - # This is the same as getting the value for "RFC822.HEADER" from #attr. - # - # [Note] - # +IMAP4rev2+ deprecates RFC822.HEADER. - def rfc822_header; attr["RFC822.HEADER"] end - - # :call-seq: rfc822_text -> String - # - # Semantically equivalent to #text, with no arguments. - # - # This is the same as getting the value for "RFC822.TEXT" from - # #attr. - # - # [Note] - # +IMAP4rev2+ deprecates RFC822.TEXT. - def rfc822_text; attr["RFC822.TEXT"] end - - # :call-seq: uid -> Integer - # - # A number expressing the unique identifier of the message. - # - # This is the same as getting the value for "UID" from #attr. - def uid; attr["UID"] end - - # :call-seq: - # binary(*part_nums, offset: nil) -> string or nil - # - # Returns the binary representation of a particular MIME part, which has - # already been decoded according to its Content-Transfer-Encoding. - # - # See #part for a description of +part_nums+ and +offset+. - # - # This is the same as getting the value of - # "BINARY[#{part_nums.join(".")}]" or - # "BINARY[#{part_nums.join(".")}]<#{offset}>" from #attr. - # - # The server must support either - # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] - # or the +BINARY+ extension - # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html]. - # - # See also: #binary_size, #mime - def binary(*part_nums, offset: nil) - attr[section_attr("BINARY", part_nums, offset: offset)] - end - - # :call-seq: - # binary_size(*part_nums) -> integer or nil - # - # Returns the decoded size of a particular MIME part (the size to expect - # in response to a BINARY fetch request). - # - # See #part for a description of +part_nums+. - # - # This is the same as getting the value of - # "BINARY.SIZE[#{part_nums.join(".")}]" from #attr. - # - # The server must support either - # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] - # or the +BINARY+ extension - # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html]. - # - # See also: #binary, #mime - def binary_size(*part_nums) - attr[section_attr("BINARY.SIZE", part_nums)] - end - - # :call-seq: modseq -> Integer - # - # The modification sequence number associated with this IMAP message. - # - # This is the same as getting the value for "MODSEQ" from #attr. - # - # The server must support the +CONDSTORE+ extension - # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. - # - # [Note] - # The +MODSEQ+ field is dynamic, and can change for a uniquely - # identified message. - def modseq; attr["MODSEQ"] end - - # :call-seq: emailid -> string or nil - # - # An ObjectID that uniquely identifies the immutable content of a single - # message. - # - # The server must return the same +EMAILID+ for both the source and - # destination messages after a COPY or MOVE command. However, it is - # possible for different messages with the same EMAILID to have different - # mutable attributes, such as flags. - # - # This is the same as getting the value for "EMAILID" from - # #attr. - # - # The server must support the +OBJECTID+ extension - # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. - def emailid; attr["EMAILID"] end - - # :call-seq: threadid -> string or nil - # - # An ObjectID that uniquely identifies a set of messages that the server - # believes should be grouped together. - # - # It is generally based on some combination of References, In-Reply-To, - # and Subject, but the exact implementation is left up to the server - # implementation. The server should return the same thread identifier for - # related messages, even if they are in different mailboxes. - # - # This is the same as getting the value for "THREADID" from - # #attr. - # - # The server must support the +OBJECTID+ extension - # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. - def threadid; attr["THREADID"] end - - private - - def body_section_attr(...) section_attr("BODY", ...) end - - def section_attr(attr, part = [], text = nil, offset: nil) - spec = Array(part).flatten.map { Integer(_1) } - spec << text if text - spec = spec.join(".") - if offset then "%s[%s]<%d>" % [attr, spec, Integer(offset)] - else "%s[%s]" % [attr, spec] - end - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/flags.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/flags.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/flags.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/flags.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,262 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - - # ------------------------------------------------------------------------- - # :section: System Flags - # - # A message has a list of zero or more named tokens, known as "flags", - # associated with it. A flag is set by its addition to this list and is - # cleared by its removal. There are two types of flags in - # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and - # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]: flags and - # keywords. A flag of either type can be permanent or session-only. - # - # A "system flag" is a message flag name that is predefined in the \IMAP - # specifications and begins with "\". Net::IMAP returns all - # system flags as symbols, without the "\" prefix. - # - # The descriptions here were copied from {[RFC-9051 - # §2.3.2]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-2.3.2]. - # See also {[RFC-3501 - # §2.3.2]}[https://www.rfc-editor.org/rfc/rfc3501.html#section-2.3.2], - # which describes the flags message attribute semantics under - # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html]. - # ------------------------------------------------------------------------- - - ## - # Flag indicating a message has been read. - SEEN = :Seen - - # Flag indicating a message has been answered. - ANSWERED = :Answered - - # A message flag indicating a message has been flagged for special or urgent - # attention. - # - # Also a mailbox special use attribute, which indicates that this mailbox - # presents all messages marked in some way as "important". When this - # special use is supported, it is likely to represent a virtual mailbox - # collecting messages (from other mailboxes) that are marked with the - # "\Flagged" message flag. - FLAGGED = :Flagged - - # Flag indicating a message has been marked for deletion. This - # will occur when the mailbox is closed or expunged. - DELETED = :Deleted - - # Flag indicating a message is only a draft or work-in-progress version. - DRAFT = :Draft - - # Flag indicating that the message is "recent," meaning that this - # session is the first session in which the client has been notified - # of this message. - # - # This flag was defined by - # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] - # and is deprecated by - # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]. - RECENT = :Recent - - # ------------------------------------------------------------------------- - # :section: Basic Mailbox Attributes - # Mailbox name attributes will be returned in #list responses. Base - # attributes must be returned according to the server's capabilities. - # - # IMAP4 specifies that all mailbox name attributes, including future - # extensions, begin with "\". Net::IMAP returns all mailbox - # attributes as symbols, without the "\" prefix. - # - # Mailbox name attributes are not case-sensitive. The current - # implementation normalizes mailbox attribute case using - # String#capitalize, such as +:Noselect+ (not +:NoSelect+). The constants - # (such as NO_SELECT) can also be used for comparison. The contants have - # been defined both with and without underscores between words. - # - # The descriptions here were copied from {[RFC-9051 § - # 7.3.1]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.1]. - # - # Other mailbox name attributes can be found in the {IANA IMAP Mailbox Name - # Attributes registry}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]. - # ------------------------------------------------------------------------- - - ## - # The +\NonExistent+ attribute indicates that a mailbox name does not refer - # to an existing mailbox. Note that this attribute is not meaningful by - # itself, as mailbox names that match the canonical #list pattern but don't - # exist must not be returned unless one of the two conditions listed below - # is also satisfied: - # - # 1. The mailbox name also satisfies the selection criteria (for example, - # it is subscribed and the "SUBSCRIBED" selection option has been - # specified). - # - # 2. "RECURSIVEMATCH" has been specified, and the mailbox name has at least - # one descendant mailbox name that does not match the #list pattern and - # does match the selection criteria. - # - # In practice, this means that the +\NonExistent+ attribute is usually - # returned with one or more of +\Subscribed+, +\Remote+, +\HasChildren+, or - # the CHILDINFO extended data item. - # - # The client must treat the presence of the +\NonExistent+ attribute as if the - # +\NoSelect+ attribute was also sent by the server - NONEXISTENT = :Nonexistent - - # Mailbox attribute indicating it is not possible for any child levels of - # hierarchy to exist under this name; no child levels exist now and none can - # be created in the future children. - # - # The client must treat the presence of the +\NoInferiors+ attribute as if the - # +\HasNoChildren+ attribute was also sent by the server - NO_INFERIORS = :Noinferiors - - # Mailbox attribute indicating it is not possible to use this name as a - # selectable mailbox. - NO_SELECT = :Noselect - - # The presence of this attribute indicates that the mailbox has child - # mailboxes. A server SHOULD NOT set this attribute if there are child - # mailboxes and the user does not have permission to access any of them. In - # this case, +\HasNoChildren+ SHOULD be used. In many cases, however, a - # server may not be able to efficiently compute whether a user has access to - # any child mailboxes. Note that even though the +\HasChildren+ attribute - # for a mailbox must be correct at the time of processing the mailbox, a - # client must be prepared to deal with a situation when a mailbox is marked - # with the +\HasChildren+ attribute, but no child mailbox appears in the - # response to the #list command. This might happen, for example, due to child - # mailboxes being deleted or made inaccessible to the user (using access - # control) by another client before the server is able to list them. - # - # It is an error for the server to return both a +\HasChildren+ and a - # +\HasNoChildren+ attribute in the same #list response. A client that - # encounters a #list response with both +\HasChildren+ and +\HasNoChildren+ - # attributes present should act as if both are absent in the #list response. - HAS_CHILDREN = :Haschildren - - # The presence of this attribute indicates that the mailbox has NO child - # mailboxes that are accessible to the currently authenticated user. - # - # It is an error for the server to return both a +\HasChildren+ and a - # +\HasNoChildren+ attribute in the same #list response. A client that - # encounters a #list response with both +\HasChildren+ and +\HasNoChildren+ - # attributes present should act as if both are absent in the #list response. - # - # Note: the +\HasNoChildren+ attribute should not be confused with the - # +\NoInferiors+ attribute, which indicates that no child mailboxes exist - # now and none can be created in the future. - HAS_NO_CHILDREN = :Hasnochildren - - # The mailbox has been marked "interesting" by the server; the mailbox - # probably contains messages that have been added since the last time the - # mailbox was selected. - # - # If it is not feasible for the server to determine whether or not the - # mailbox is "interesting", the server SHOULD NOT send either +\Marked+ or - # +\Unmarked+. The server MUST NOT send more than one of +\Marked+, - # +\Unmarked+, and +\NoSelect+ for a single mailbox, and it MAY send none of - # these. - MARKED = :Marked - - # The mailbox does not contain any additional messages since the last time - # the mailbox was selected. - # - # If it is not feasible for the server to determine whether or not the - # mailbox is "interesting", the server SHOULD NOT send either +\Marked+ or - # +\Unmarked+. The server MUST NOT send more than one of +\Marked+, - # +\Unmarked+, and +\NoSelect+ for a single mailbox, and it MAY send none of - # these. - UNMARKED = :Unmarked - - # The mailbox name was subscribed to using the #subscribe command. - SUBSCRIBED = :Subscribed - - # The mailbox is a remote mailbox. - REMOTE = :Remove - - # Alias for NO_INFERIORS, to match the \IMAP spelling. - NOINFERIORS = NO_INFERIORS - # Alias for NO_SELECT, to match the \IMAP spelling. - NOSELECT = NO_SELECT - # Alias for HAS_CHILDREN, to match the \IMAP spelling. - HASCHILDREN = HAS_CHILDREN - # Alias for HAS_NO_CHILDREN, to match the \IMAP spelling. - HASNOCHILDREN = HAS_NO_CHILDREN - - # ------------------------------------------------------------------------- - # :section: Mailbox role attributes - # - # Mailbox name attributes will be returned in #list responses. In addition - # to the base mailbox name attributes defined above, an \IMAP server MAY - # also include any or all of the following attributes that denote "role" (or - # "special-use") of a mailbox. These attributes are included along with base - # attributes defined above. A given mailbox may have none, one, or more than - # one of these attributes. In some cases, a special use is advice to a - # client about what to put in that mailbox. In other cases, it's advice to a - # client about what to expect to find there. - # - # IMAP4 specifies that all mailbox name attributes, including future - # extensions, begin with "\". Net::IMAP returns all mailbox - # attributes as symbols, without the "\" prefix. - # - # The special use attributes were first defined as part of the - # SPECIAL-USE[https://www.rfc-editor.org/rfc/rfc6154.html] extension, but - # servers may return them without including the +SPECIAL-USE+ #capability. - # - # The descriptions here were copied from {[RFC-9051 § - # 7.3.1]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.1]. - # - # Other mailbox name attributes can be found in the {IANA IMAP Mailbox Name - # Attributes registry}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]. - # ------------------------------------------------------------------------- - - # Mailbox attribute indicating that this mailbox presents all messages in - # the user's message store. Implementations MAY omit some messages, such as, - # perhaps, those in \Trash and \Junk. When this special use is supported, it - # is almost certain to represent a virtual mailbox - ALL = :All - - # Mailbox attribute indicating that this mailbox is used to archive - # messages. The meaning of an "archival" mailbox is server dependent; - # typically, it will be used to get messages out of the inbox, or otherwise - # keep them out of the user's way, while still making them accessible - ARCHIVE = :Archive - - # Mailbox attribute indicating that this mailbox is used to hold draft - # messages -- typically, messages that are being composed but have not yet - # been sent. In some server implementations, this might be a virtual - # mailbox, containing messages from other mailboxes that are marked with the - # "\Draft" message flag. Alternatively, this might just be advice that a - # client put drafts here - DRAFTS = :Drafts - - #-- - # n.b. FLAGGED is defined in the system flags section. - #++ - - # Mailbox attribute indicating that this mailbox is where messages deemed to - # be junk mail are held. Some server implementations might put messages here - # automatically. Alternatively, this might just be advice to a client-side - # spam filter. - JUNK = :Junk - - # Mailbox attribute indicating that this mailbox is used to hold copies of - # messages that have been sent. Some server implementations might put - # messages here automatically. Alternatively, this might just be advice that - # a client save sent messages here. - SENT = :Sent - - # Mailbox attribute indicating that this mailbox is used to hold messages - # that have been deleted or marked for deletion. In some server - # implementations, this might be a virtual mailbox, containing messages from - # other mailboxes that are marked with the +\Deleted+ message flag. - # Alternatively, this might just be advice that a client that chooses not to - # use the \IMAP +\Deleted+ model should use as its trash location. In server - # implementations that strictly expect the \IMAP +\Deleted+ model, this - # special use is likely not to be supported. - TRASH = :Trash - - # :section: - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_data.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_data.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_data.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_data.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,1210 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - autoload :FetchData, "#{__dir__}/fetch_data" - autoload :SearchResult, "#{__dir__}/search_result" - autoload :SequenceSet, "#{__dir__}/sequence_set" - - # Net::IMAP::ContinuationRequest represents command continuation requests. - # - # The command continuation request response is indicated by a "+" token - # instead of a tag. This form of response indicates that the server is - # ready to accept the continuation of a command from the client. The - # remainder of this response is a line of text. - # - class ContinuationRequest < Struct.new(:data, :raw_data) - ## - # method: data - # :call-seq: data -> ResponseText - # - # Returns a ResponseText object - - ## - # method: raw_data - # :call-seq: raw_data -> string - # - # the raw response data - end - - # Net::IMAP::UntaggedResponse represents untagged responses. - # - # Data transmitted by the server to the client and status responses - # that do not indicate command completion are prefixed with the token - # "*", and are called untagged responses. - # - class UntaggedResponse < Struct.new(:name, :data, :raw_data) - ## - # method: name - # :call-seq: name -> string - # - # The uppercase response name, e.g. "FLAGS", "LIST", "FETCH", etc. - - ## - # method: data - # :call-seq: data -> object or nil - # - # The parsed response data, e.g: an array of flag symbols, an array of - # capabilities strings, a ResponseText object, a MailboxList object, a - # FetchData object, a Namespaces object, etc. The response #name - # determines what form the data can take. - - ## - # method: raw_data - # :call-seq: raw_data -> string - # - # The raw response data. - end - - # Net::IMAP::IgnoredResponse represents intentionally ignored responses. - # - # This includes untagged response "NOOP" sent by eg. Zimbra to avoid - # some clients to close the connection. - # - # It matches no IMAP standard. - class IgnoredResponse < UntaggedResponse - end - - # **Note:** This represents an intentionally _unstable_ API. Where - # instances of this class are returned, future releases may return a - # different (incompatible) object without deprecation or warning. - # - # Net::IMAP::UnparsedData represents data for unknown response types or - # unknown extensions to response types without a well-defined extension - # grammar. - # - # See also: UnparsedNumericResponseData, ExtensionData, IgnoredResponse - class UnparsedData < Struct.new(:unparsed_data) - ## - # method: unparsed_data - # :call-seq: unparsed_data -> string - # - # The unparsed data - end - - # **Note:** This represents an intentionally _unstable_ API. Where - # instances of this class are returned, future releases may return a - # different (incompatible) object without deprecation or warning. - # - # Net::IMAP::UnparsedNumericResponseData represents data for unhandled - # response types with a numeric prefix. See the documentation for #number. - # - # See also: UnparsedData, ExtensionData, IgnoredResponse - class UnparsedNumericResponseData < Struct.new(:number, :unparsed_data) - ## - # method: number - # :call-seq: number -> integer - # - # Returns a numeric response data prefix, when available. - # - # Many response types are prefixed with a non-negative #number. For - # message data, #number may represent a sequence number or a UID. For - # mailbox data, #number may represent a message count. - - ## - # method: unparsed_data - # :call-seq: unparsed_data -> string - # - # The unparsed data, not including #number or UntaggedResponse#name. - end - - # **Note:** This represents an intentionally _unstable_ API. Where - # instances of this class are returned, future releases may return a - # different (incompatible) object without deprecation or warning. - # - # Net::IMAP::ExtensionData represents data that is parsable according to the - # forward-compatible extension syntax in RFC3501, RFC4466, or RFC9051, but - # isn't directly known or understood by Net::IMAP yet. - # - # See also: UnparsedData, UnparsedNumericResponseData, IgnoredResponse - class ExtensionData < Struct.new(:data) - ## - # method: data - # :call-seq: data -> string - # - # The parsed extension data. - end - - # Net::IMAP::TaggedResponse represents tagged responses. - # - # The server completion result response indicates the success or - # failure of the operation. It is tagged with the same tag as the - # client command which began the operation. - # - class TaggedResponse < Struct.new(:tag, :name, :data, :raw_data) - ## - # method: tag - # :call-seq: tag -> string - # - # Returns the command tag - - ## - # method: name - # :call-seq: name -> string - # - # Returns the name, one of "OK", "NO", or "BAD". - - ## - # method: data - # :call-seq: data -> ResponseText - # - # Returns a ResponseText object - - ## - # method: raw_data - # :call-seq: raw_data -> string - # - # The raw response data. - end - - # Net::IMAP::ResponseText represents texts of responses. - # - # The text may be prefixed by a ResponseCode. - # - # ResponseText is returned from TaggedResponse#data, or from - # UntaggedResponse#data when the response type is a "condition" ("OK", "NO", - # "BAD", "PREAUTH", or "BYE"). - class ResponseText < Struct.new(:code, :text) - # Used to avoid an allocation when ResponseText is empty - EMPTY = new(nil, "").freeze - - ## - # method: code - # :call-seq: code -> ResponseCode or nil - # - # Returns a ResponseCode, if the response contains one - - ## - # method: text - # :call-seq: text -> string - # - # Returns the response text, not including any response code - end - - # Net::IMAP::ResponseCode represents response codes. Response codes can be - # retrieved from ResponseText#code and can be included in any "condition" - # response: any TaggedResponse and UntaggedResponse when the response type - # is a "condition" ("OK", "NO", "BAD", "PREAUTH", or "BYE"). - # - # Some response codes come with additional data which will be parsed by - # Net::IMAP. Others return +nil+ for #data, but are used as a - # machine-readable annotation for the human-readable ResponseText#text in - # the same response. When Net::IMAP does not know how to parse response - # code text, #data returns the unparsed string. - # - # Untagged response code #data is pushed directly onto Net::IMAP#responses, - # keyed by #name, unless it is removed by the command that generated it. - # Use Net::IMAP#add_response_handler to view tagged response codes for - # command methods that do not return their TaggedResponse. - # - # \IMAP extensions may define new codes and the data that comes with them. - # The IANA {IMAP Response - # Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml] - # registry has links to specifications for all standard response codes. - # Response codes are backwards compatible: Servers are allowed to send new - # response codes even if the client has not enabled the extension that - # defines them. When unknown response code data is encountered, #data - # will return an unparsed string. - # - # ==== +IMAP4rev1+ Response Codes - # See [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501]] {§7.1, "Server - # Responses - Status - # Responses"}[https://www.rfc-editor.org/rfc/rfc3501#section-7.1] for full - # definitions of the basic set of IMAP4rev1 response codes: - # * +ALERT+, the ResponseText#text contains a special alert that MUST be - # brought to the user's attention. - # * +BADCHARSET+, #data will be an array of charset strings, or +nil+. - # * +CAPABILITY+, #data will be an array of capability strings. - # * +PARSE+, the ResponseText#text presents an error parsing a message's - # \[RFC5322] or [MIME-IMB] headers. - # * +PERMANENTFLAGS+, followed by an array of flags. System flags will be - # symbols, and keyword flags will be strings. See - # rdoc-ref:Net::IMAP@System+flags - # * +READ-ONLY+, the mailbox was selected read-only, or changed to read-only - # * +READ-WRITE+, the mailbox was selected read-write, or changed to - # read-write - # * +TRYCREATE+, when #append or #copy fail because the target mailbox - # doesn't exist. - # * +UIDNEXT+, #data is an Integer, the next UID value of the mailbox. See - # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]], - # {§2.3.1.1, "Unique Identifier (UID) Message - # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1]. - # * +UIDVALIDITY+, #data is an Integer, the UID validity value of the - # mailbox. See [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]], - # {§2.3.1.1, "Unique Identifier (UID) Message - # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1]. - # * +UNSEEN+, #data is an Integer, the number of messages which do not have - # the \Seen flag set. - # DEPRECATED by IMAP4rev2. - # - # ==== +BINARY+ extension - # See {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516]. - # * +UNKNOWN-CTE+, with a tagged +NO+ response, when the server does not - # known how to decode a CTE (content-transfer-encoding). #data is +nil+. - # See IMAP#fetch. - # - # ==== +UIDPLUS+ extension - # See {[RFC4315 §3]}[https://www.rfc-editor.org/rfc/rfc4315#section-3]. - # * +APPENDUID+, #data is UIDPlusData. See IMAP#append. - # * +COPYUID+, #data is UIDPlusData. See IMAP#copy. - # * +UIDNOTSTICKY+, #data is +nil+. See IMAP#select. - # - # ==== +SEARCHRES+ extension - # See {[RFC5182]}[https://www.rfc-editor.org/rfc/rfc5182]. - # * +NOTSAVED+, with a tagged +NO+ response, when the search result variable - # is not saved. #data is +nil+. - # - # ==== +RFC5530+ Response Codes - # See {[RFC5530]}[https://www.rfc-editor.org/rfc/rfc5530], "IMAP Response - # Codes" for the definition of the following response codes, which are all - # machine-readable annotations for the human-readable ResponseText#text, and - # have +nil+ #data of their own: - # * +UNAVAILABLE+ - # * +AUTHENTICATIONFAILED+ - # * +AUTHORIZATIONFAILED+ - # * +EXPIRED+ - # * +PRIVACYREQUIRED+ - # * +CONTACTADMIN+ - # * +NOPERM+ - # * +INUSE+ - # * +EXPUNGEISSUED+ - # * +CORRUPTION+ - # * +SERVERBUG+ - # * +CLIENTBUG+ - # * +CANNOT+ - # * +LIMIT+ - # * +OVERQUOTA+ - # * +ALREADYEXISTS+ - # * +NONEXISTENT+ - # - # ==== +QRESYNC+ extension - # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. - # * +CLOSED+, returned when the currently selected mailbox is closed - # implicity by selecting or examining another mailbox. #data is +nil+. - # - # ==== +IMAP4rev2+ Response Codes - # See {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051] {§7.1, "Server - # Responses - Status - # Responses"}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1] for full - # descriptions of IMAP4rev2 response codes. IMAP4rev2 includes all of the - # response codes listed above (except "UNSEEN") and adds the following: - # * +HASCHILDREN+, with a tagged +NO+ response, when a mailbox delete failed - # because the server doesn't allow deletion of mailboxes with children. - # #data is +nil+. - # - # ==== +CONDSTORE+ extension - # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. - # * +NOMODSEQ+, when selecting a mailbox that does not support - # mod-sequences. #data is +nil+. See IMAP#select. - # * +HIGHESTMODSEQ+, #data is an Integer, the highest mod-sequence value of - # all messages in the mailbox. See IMAP#select. - # * +MODIFIED+, #data is a SequenceSet, the messages that have been modified - # since the +UNCHANGEDSINCE+ mod-sequence given to +STORE+ or UID - # STORE. - # - # ==== +OBJECTID+ extension - # See {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. - # * +MAILBOXID+, #data is a string - # - class ResponseCode < Struct.new(:name, :data) - ## - # method: name - # :call-seq: name -> string - # - # Returns the response code name, such as "ALERT", "PERMANENTFLAGS", or - # "UIDVALIDITY". - - ## - # method: data - # :call-seq: data -> object or nil - # - # Returns the parsed response code data, e.g: an array of capabilities - # strings, an array of character set strings, a list of permanent flags, - # an Integer, etc. The response #code determines what form the response - # code data can take. - end - - # Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies - # the +APPENDUID+ and +COPYUID+ response codes. - # - # See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]]. - # - # ==== Capability requirement - # - # The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported. - # A server that supports +UIDPLUS+ should send a UIDPlusData object inside - # every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append], - # copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid - # copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid - # move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination - # mailbox reports +UIDNOTSTICKY+. - # - #-- - # TODO: support MULTIAPPEND - #++ - # - class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids) - ## - # method: uidvalidity - # :call-seq: uidvalidity -> nonzero uint32 - # - # The UIDVALIDITY of the destination mailbox. - - ## - # method: source_uids - # :call-seq: source_uids -> nil or an array of nonzero uint32 - # - # The UIDs of the copied or moved messages. - # - # Note:: Returns +nil+ for Net::IMAP#append. - - ## - # method: assigned_uids - # :call-seq: assigned_uids -> an array of nonzero uint32 - # - # The newly assigned UIDs of the copied, moved, or appended messages. - # - # Note:: This always returns an array, even when it contains only one UID. - - ## - # :call-seq: uid_mapping -> nil or a hash - # - # Returns a hash mapping each source UID to the newly assigned destination - # UID. - # - # Note:: Returns +nil+ for Net::IMAP#append. - def uid_mapping - source_uids&.zip(assigned_uids)&.to_h - end - end - - # Net::IMAP::MailboxList represents contents of the LIST response, - # representing a single mailbox path. - # - # Net::IMAP#list returns an array of MailboxList objects. - # - class MailboxList < Struct.new(:attr, :delim, :name) - ## - # method: attr - # :call-seq: attr -> array of Symbols - # - # Returns the name attributes. Each name attribute is a symbol capitalized - # by String#capitalize, such as :Noselect (not :NoSelect). For the - # semantics of each attribute, see: - # * rdoc-ref:Net::IMAP@Basic+Mailbox+Attributes - # * rdoc-ref:Net::IMAP@Mailbox+role+Attributes - # * Net::IMAP@SPECIAL-USE - # * The IANA {IMAP Mailbox Name Attributes - # registry}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml] - - ## - # method: delim - # :call-seq: delim -> single character string - # - # Returns the hierarchy delimiter for the mailbox path. - - ## - # method: name - # :call-seq: name -> string - # - # Returns the mailbox name. - end - - # Net::IMAP::MailboxQuota represents contents of GETQUOTA response. - # This object can also be a response to GETQUOTAROOT. In the syntax - # specification below, the delimiter used with the "#" construct is a - # single space (SPACE). - # - # Net:IMAP#getquota returns an array of MailboxQuota objects. - # - # Net::IMAP#getquotaroot returns an array containing both MailboxQuotaRoot - # and MailboxQuota objects. - # - class MailboxQuota < Struct.new(:mailbox, :usage, :quota) - ## - # method: mailbox - # :call-seq: mailbox -> string - # - # The mailbox with the associated quota. - - ## - # method: usage - # :call-seq: usage -> Integer - # - # Current storage usage of the mailbox. - - ## - # method: quota - # :call-seq: quota -> Integer - # - # Quota limit imposed on the mailbox. - # - end - - # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT - # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.) - # - # Net::IMAP#getquotaroot returns an array containing both MailboxQuotaRoot - # and MailboxQuota objects. - # - class MailboxQuotaRoot < Struct.new(:mailbox, :quotaroots) - ## - # method: mailbox - # :call-seq: mailbox -> string - # - # The mailbox with the associated quota. - - ## - # method: mailbox - # :call-seq: quotaroots -> array of strings - # - # Zero or more quotaroots that affect the quota on the specified mailbox. - end - - # Net::IMAP::MailboxACLItem represents the response from GETACL. - # - # Net::IMAP#getacl returns an array of MailboxACLItem objects. - # - # ==== Required capability - # +ACL+ - described in [ACL[https://tools.ietf.org/html/rfc4314]] - class MailboxACLItem < Struct.new(:user, :rights, :mailbox) - ## - # method: mailbox - # :call-seq: mailbox -> string - # - # The mailbox to which the indicated #user has the specified #rights. - - ## - # method: user - # :call-seq: user -> string - # - # Login name that has certain #rights to the #mailbox that was specified - # with the getacl command. - - ## - # method: rights - # :call-seq: rights -> string - # - # The access rights the indicated #user has to the #mailbox. - end - - # Net::IMAP::Namespace represents a single namespace contained inside a - # NAMESPACE response. - # - # Returned by Net::IMAP#namespace, contained inside a Namespaces object. - # - class Namespace < Struct.new(:prefix, :delim, :extensions) - ## - # method: prefix - # :call-seq: prefix -> string - # - # Returns the namespace prefix string. - - ## - # method: delim - # :call-seq: delim -> single character string or nil - # - # Returns a hierarchy delimiter character, if it exists. - - ## - # method: extensions - # :call-seq: extensions -> Hash[String, Array[String]] - # - # A hash of parameters mapped to arrays of strings, for extensibility. - # Extension parameter semantics would be defined by the extension. - end - - # Net::IMAP::Namespaces represents a +NAMESPACE+ server response, which - # contains lists of #personal, #shared, and #other namespaces. - # - # Net::IMAP#namespace returns a Namespaces object. - # - class Namespaces < Struct.new(:personal, :other, :shared) - ## - # method: personal - # :call-seq: personal -> array of Namespace - # - # Returns an array of Personal Namespace objects. - - ## - # method: other - # :call-seq: other -> array of Namespace - # - # Returns an array of Other Users' Namespace objects. - - ## - # method: shared - # :call-seq: shared -> array of Namespace - # - # Returns an array of Shared Namespace objects. - end - - # Net::IMAP::StatusData represents the contents of the STATUS response. - # - # Net::IMAP#status returns the contents of #attr. - class StatusData < Struct.new(:mailbox, :attr) - ## - # method: mailbox - # :call-seq: mailbox -> string - # - # The mailbox name. - - ## - # method: attr - # :call-seq: attr -> Hash[String, Integer] - # - # A hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT", - # "UIDVALIDITY", "UNSEEN". Each value is a number. - end - - # Net::IMAP::Envelope represents envelope structures of messages. - # - # [Note] - # When the #sender and #reply_to fields are absent or empty, they will - # return the same value as #from. Also, fields may return values that are - # invalid for well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] - # messages when the message is malformed or a draft message. - # - # See [{IMAP4rev1 §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2]] - # and [{IMAP4rev2 §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2]] - # for full description of the envelope fields, and - # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. - # - # Returned by FetchData#envelope - class Envelope < Struct.new(:date, :subject, :from, :sender, :reply_to, - :to, :cc, :bcc, :in_reply_to, :message_id) - ## - # method: date - # call-seq: date -> string - # - # Returns a string that represents the +Date+ header. - # - # [Note] - # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] - # message, the #date field must not be +nil+. However it can be +nil+ - # for a malformed or draft message. - - ## - # method: subject - # call-seq: subject -> string or nil - # - # Returns a string that represents the +Subject+ header, if it is present. - # - # [Note] - # Servers should return +nil+ when the header is absent and an empty - # string when it is present but empty. Some servers may return a +nil+ - # envelope member in the "present but empty" case. Clients should treat - # +nil+ and empty string as identical. - - ## - # method: from - # call-seq: from -> array of Net::IMAP::Address or nil - # - # Returns an array of Address that represents the +From+ header. - # - # If the +From+ header is absent, or is present but empty, the server - # returns +nil+ for this envelope field. - # - # [Note] - # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] - # message, the #from field must not be +nil+. However it can be +nil+ - # for a malformed or draft message. - - ## - # method: sender - # call-seq: sender -> array of Net::IMAP::Address or nil - # - # Returns an array of Address that represents the +Sender+ header. - # - # [Note] - # If the Sender header is absent, or is present but empty, the - # server sets this field to be the same value as #from. Therefore, in a - # well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] message, - # the #sender envelope field must not be +nil+. However it can be - # +nil+ for a malformed or draft message. - - ## - # method: reply_to - # call-seq: reply_to -> array of Net::IMAP::Address or nil - # - # Returns an array of Address that represents the Reply-To - # header. - # - # [Note] - # If the Reply-To header is absent, or is present but empty, - # the server sets this field to be the same value as #from. Therefore, - # in a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] - # message, the #reply_to envelope field must not be +nil+. However it - # can be +nil+ for a malformed or draft message. - - ## - # method: to - # call-seq: to -> array of Net::IMAP::Address - # - # Returns an array of Address that represents the +To+ header. - - ## - # method: cc - # call-seq: cc -> array of Net::IMAP::Address - # - # Returns an array of Address that represents the +Cc+ header. - - ## - # method: bcc - # call-seq: bcc -> array of Net::IMAP::Address - # - # Returns an array of Address that represents the +Bcc+ header. - - ## - # method: in_reply_to - # call-seq: in_reply_to -> string - # - # Returns a string that represents the In-Reply-To header. - # - # [Note] - # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] - # message, the #in_reply_to field, if present, must not be empty. But - # it can still return an empty string for malformed messages. - # - # Servers should return +nil+ when the header is absent and an empty - # string when it is present but empty. Some servers may return a +nil+ - # envelope member in the "present but empty" case. Clients should treat - # +nil+ and empty string as identical. - - ## - # method: message_id - # call-seq: message_id -> string - # - # Returns a string that represents the Message-ID. - # - # [Note] - # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] - # message, the #message_id field, if present, must not be empty. But it - # can still return an empty string for malformed messages. - # - # Servers should return +nil+ when the header is absent and an empty - # string when it is present but empty. Some servers may return a +nil+ - # envelope member in the "present but empty" case. Clients should treat - # +nil+ and empty string as identical. - end - - # Net::IMAP::Address represents an electronic mail address, which has been - # parsed into its component parts by the server. Address objects are - # returned within Envelope fields. - # - # === Group syntax - # - # When the #host field is +nil+, this is a special form of address structure - # that indicates the [RFC5322[https://tools.ietf.org/html/rfc5322]] group - # syntax. If the #mailbox name field is also +nil+, this is an end-of-group - # marker (semicolon in RFC-822 syntax). If the #mailbox name field is - # non-+NIL+, this is the start of a group marker, and the mailbox #name - # field holds the group name phrase. - class Address < Struct.new(:name, :route, :mailbox, :host) - ## - # method: name - # :call-seq: name -> string or nil - # - # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] address - # +display-name+ (or the mailbox +phrase+ in the RFC-822 grammar). - - ## - # method: route - # :call-seq: route -> string or nil - # - # Returns the route from RFC-822 route-addr. - # - # Note:: Generating this obsolete route addressing syntax is not allowed - # by [RFC5322[https://tools.ietf.org/html/rfc5322]]. However, - # addresses with this syntax must still be accepted and parsed. - - ## - # method: mailbox - # :call-seq: mailbox -> string or nil - # - # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] address - # +local-part+, if #host is not +nil+. - # - # When #host is +nil+, this returns - # an [RFC5322[https://tools.ietf.org/html/rfc5322]] group name and a +nil+ - # mailbox indicates the end of a group. - - ## - # method: host - # :call-seq: host -> string or nil - # - # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] addr-spec - # +domain+ name. - # - # +nil+ indicates [RFC5322[https://tools.ietf.org/html/rfc5322]] group - # syntax. - end - - # Net::IMAP::ContentDisposition represents Content-Disposition fields. - # - class ContentDisposition < Struct.new(:dsp_type, :param) - ## - # method: dsp_type - # :call-seq: dsp_type -> string - # - # Returns the content disposition type, as defined by - # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. - - ## - # method: param - # :call-seq: param -> hash - # - # Returns a hash representing parameters of the Content-Disposition - # field, as defined by [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. - end - - # Net::IMAP::ThreadMember represents a thread-node returned - # by Net::IMAP#thread. - # - class ThreadMember < Struct.new(:seqno, :children) - ## - # method: seqno - # :call-seq: seqno -> Integer - # - # The message sequence number. - - ## - # method: children - # :call-seq: children -> array of ThreadMember - # - # An array of Net::IMAP::ThreadMember objects for mail items that are - # children of this in the thread. - - # Returns a SequenceSet containing #seqno and all #children's seqno, - # recursively. - def to_sequence_set - SequenceSet.new all_seqnos - end - - protected - - def all_seqnos(node = self) - [node.seqno].concat node.children.flat_map { _1.all_seqnos } - end - - end - - # Net::IMAP::BodyStructure is included by all of the structs that can be - # returned from a "BODYSTRUCTURE" or "BODY" - # FetchData#attr value. Although these classes don't share a base class, - # this module can be used to pattern match all of them. - # - # See {[IMAP4rev1] §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] - # and {[IMAP4rev2] §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2-4.9] - # for full description of all +BODYSTRUCTURE+ fields, and also - # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. - # - # === Classes that include BodyStructure - # BodyTypeBasic:: Represents any message parts that are not handled by - # BodyTypeText, BodyTypeMessage, or BodyTypeMultipart. - # BodyTypeText:: Used by text/* parts. Contains all of the - # BodyTypeBasic fields. - # BodyTypeMessage:: Used by message/rfc822 and - # message/global parts. Contains all of the - # BodyTypeBasic fields. Other message/* types - # should use BodyTypeBasic. - # BodyTypeMultipart:: for multipart/* parts - # - module BodyStructure - end - - # Net::IMAP::BodyTypeBasic represents basic body structures of messages and - # message parts, unless they have a Content-Type that is handled by - # BodyTypeText, BodyTypeMessage, or BodyTypeMultipart. - # - # See {[IMAP4rev1] §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] - # and {[IMAP4rev2] §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2-4.9] - # for full description of all +BODYSTRUCTURE+ fields, and also - # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. - # - class BodyTypeBasic < Struct.new(:media_type, :subtype, - :param, :content_id, - :description, :encoding, :size, - :md5, :disposition, :language, - :location, - :extension) - include BodyStructure - - ## - # method: media_type - # :call-seq: media_type -> string - # - # The top-level media type as defined in - # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. - - ## - # method: subtype - # :call-seq: subtype -> string - # - # The media subtype name as defined in - # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. - - ## - # method: param - # :call-seq: param -> string - # - # Returns a hash that represents parameters as defined in - # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. - - ## - # method: content_id - # :call-seq: content_id -> string - # - # Returns a string giving the content id as defined - # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] - # {§7}[https://tools.ietf.org/html/rfc2045#section-7]. - - ## - # method: description - # :call-seq: description -> string - # - # Returns a string giving the content description as defined - # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] - # {§8}[https://tools.ietf.org/html/rfc2045#section-8]. - - ## - # method: encoding - # :call-seq: encoding -> string - # - # Returns a string giving the content transfer encoding as defined - # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] - # {§6}[https://tools.ietf.org/html/rfc2045#section-6]. - - ## - # method: size - # :call-seq: size -> integer - # - # Returns a number giving the size of the body in octets. - - ## - # method: md5 - # :call-seq: md5 -> string - # - # Returns a string giving the body MD5 value as defined in - # [MD5[https://tools.ietf.org/html/rfc1864]]. - - ## - # method: disposition - # :call-seq: disposition -> ContentDisposition - # - # Returns a ContentDisposition object giving the content - # disposition, as defined by - # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. - - ## - # method: language - # :call-seq: language -> string - # - # Returns a string or an array of strings giving the body - # language value as defined in - # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]. - - #-- - ## - # method: location - # :call-seq: location -> string - # - # A string list giving the body content URI as defined in - # [LOCATION[https://www.rfc-editor.org/info/rfc2557]]. - #++ - - ## - # method: extension - # :call-seq: extension -> string - # - # Returns extension data. The +BODYSTRUCTURE+ fetch attribute - # contains extension data, but +BODY+ does not. - - ## - # :call-seq: multipart? -> false - # - # BodyTypeBasic is not used for multipart MIME parts. - def multipart? - return false - end - - # :call-seq: media_subtype -> subtype - # - # >>> - # [Obsolete] - # Use +subtype+ instead. Calling this will generate a warning message - # to +stderr+, then return the value of +subtype+. - #-- - # TODO: why not just keep this as an alias? Would "media_subtype" be used - # for something else? - #++ - def media_subtype - warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) - return subtype - end - end - - # Net::IMAP::BodyTypeText represents the body structures of messages and - # message parts, when Content-Type is text/*. - # - # BodyTypeText contains all of the fields of BodyTypeBasic. See - # BodyTypeBasic for documentation of the following: - # * {media_type}[rdoc-ref:BodyTypeBasic#media_type] - # * subtype[rdoc-ref:BodyTypeBasic#subtype] - # * param[rdoc-ref:BodyTypeBasic#param] - # * {content_id}[rdoc-ref:BodyTypeBasic#content_id] - # * description[rdoc-ref:BodyTypeBasic#description] - # * encoding[rdoc-ref:BodyTypeBasic#encoding] - # * size[rdoc-ref:BodyTypeBasic#size] - # - class BodyTypeText < Struct.new(:media_type, :subtype, - :param, :content_id, - :description, :encoding, :size, - :lines, - :md5, :disposition, :language, - :location, - :extension) - include BodyStructure - - ## - # method: lines - # :call-seq: lines -> Integer - # - # Returns the size of the body in text lines. - - ## - # :call-seq: multipart? -> false - # - # BodyTypeText is not used for multipart MIME parts. - def multipart? - return false - end - - # Obsolete: use +subtype+ instead. Calling this will - # generate a warning message to +stderr+, then return - # the value of +subtype+. - def media_subtype - warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) - return subtype - end - end - - # Net::IMAP::BodyTypeMessage represents the body structures of messages and - # message parts, when Content-Type is message/rfc822 or - # message/global. - # - # BodyTypeMessage contains all of the fields of BodyTypeBasic. See - # BodyTypeBasic for documentation of the following fields: - # * {media_type}[rdoc-ref:BodyTypeBasic#media_type] - # * subtype[rdoc-ref:BodyTypeBasic#subtype] - # * param[rdoc-ref:BodyTypeBasic#param] - # * {content_id}[rdoc-ref:BodyTypeBasic#content_id] - # * description[rdoc-ref:BodyTypeBasic#description] - # * encoding[rdoc-ref:BodyTypeBasic#encoding] - # * size[rdoc-ref:BodyTypeBasic#size] - class BodyTypeMessage < Struct.new(:media_type, :subtype, - :param, :content_id, - :description, :encoding, :size, - :envelope, :body, :lines, - :md5, :disposition, :language, - :location, - :extension) - include BodyStructure - - ## - # method: envelope - # :call-seq: envelope -> Envelope - # - # Returns a Net::IMAP::Envelope giving the envelope structure. - - ## - # method: body - # :call-seq: body -> BodyStructure - # - # Returns a Net::IMAP::BodyStructure for the message's body structure. - - ## - # :call-seq: multipart? -> false - # - # BodyTypeMessage is not used for multipart MIME parts. - def multipart? - return false - end - - # Obsolete: use +subtype+ instead. Calling this will - # generate a warning message to +stderr+, then return - # the value of +subtype+. - def media_subtype - warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) - return subtype - end - end - - # BodyTypeAttachment is not used and will be removed in an upcoming release. - # - # === Bug Analysis - # - # \IMAP body structures are parenthesized lists and assign their fields - # positionally, so missing fields change the intepretation of all - # following fields. Additionally, different body types have a different - # number of required fields, followed by optional "extension" fields. - # - # BodyTypeAttachment was previously returned when a "message/rfc822" part, - # which should be sent as body-type-msg with ten required fields, - # was actually sent as a body-type-basic with _seven_ required - # fields. - # - # basic => type, subtype, param, id, desc, enc, octets, md5=nil, dsp=nil, lang=nil, loc=nil, *ext - # msg => type, subtype, param, id, desc, enc, octets, envelope, body, lines, md5=nil, ... - # - # Normally, +envelope+ and +md5+ are incompatible, but Net::IMAP leniently - # allowed buggy servers to send +NIL+ for +envelope+. As a result, when a - # server sent a message/rfc822 part with +NIL+ for +md5+ and a - # non-NIL +dsp+, Net::IMAP mis-interpreted the - # Content-Disposition as if it were a strange body type. In all - # reported cases, the Content-Disposition was "attachment", so - # BodyTypeAttachment was created as the workaround. - # - # === Current behavior - # - # When interpreted strictly, +envelope+ and +md5+ are incompatible. So the - # current parsing algorithm peeks ahead after it has recieved the seventh - # body field. If the next token is not the start of an +envelope+, we assume - # the server has incorrectly sent us a body-type-basic and return - # BodyTypeBasic. As a result, what was previously BodyTypeMessage#body => - # BodyTypeAttachment is now BodyTypeBasic#disposition => ContentDisposition. - # - class BodyTypeAttachment < Struct.new(:dsp_type, :_unused_, :param) - # *invalid for BodyTypeAttachment* - def media_type - warn(<<~WARN, uplevel: 1) - BodyTypeAttachment#media_type is obsolete. Use dsp_type instead. - WARN - dsp_type - end - - # *invalid for BodyTypeAttachment* - def subtype - warn("BodyTypeAttachment#subtype is obsolete.\n", uplevel: 1) - nil - end - - ## - # method: dsp_type - # :call-seq: dsp_type -> string - # - # Returns the content disposition type, as defined by - # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. - - ## - # method: param - # :call-seq: param -> hash - # - # Returns a hash representing parameters of the Content-Disposition - # field, as defined by [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. - - ## - def multipart? - return false - end - end - - deprecate_constant :BodyTypeAttachment - - # Net::IMAP::BodyTypeMultipart represents body structures of messages and - # message parts, when Content-Type is multipart/*. - class BodyTypeMultipart < Struct.new(:media_type, :subtype, - :parts, - :param, :disposition, :language, - :location, - :extension) - include BodyStructure - - ## - # method: media_type - # call-seq: media_type -> "multipart" - # - # BodyTypeMultipart is only used with multipart/* media types. - - ## - # method: subtype - # call-seq: subtype -> string - # - # Returns the content subtype name - # as defined in [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. - - ## - # method: parts - # call-seq: parts -> array of BodyStructure objects - # - # Returns an array with a BodyStructure object for each part contained in - # this part. - - ## - # method: param - # call-seq: param -> hash - # - # Returns a hash that represents parameters - # as defined in [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. - - ## - # method: disposition - # call-seq: disposition -> ContentDisposition - # - # Returns a Net::IMAP::ContentDisposition object giving the content - # disposition. - - ## - # method: language - # :call-seq: language -> string - # - # Returns a string or an array of strings giving the body - # language value as defined in - # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]. - - ## - # method: extension - # call-seq: extension -> array - # - # Returns extension data as an array of numbers strings, and nested - # arrays (of numbers, strings, etc). - - ## - # :call-seq: multipart? -> true - # - # BodyTypeMultipart is used for multipart MIME parts. - def multipart? - return true - end - - ## - # Obsolete: use +subtype+ instead. Calling this will - # generate a warning message to +stderr+, then return - # the value of +subtype+. - def media_subtype - warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) - return subtype - end - end - - # === Obsolete - # BodyTypeExtension is not used and will be removed in an upcoming release. - # - # >>> - # BodyTypeExtension was (incorrectly) used for message/* parts - # (besides message/rfc822, which correctly uses BodyTypeMessage). - # - # Net::IMAP now (correctly) parses all message types (other than - # message/rfc822 or message/global) as BodyTypeBasic. - class BodyTypeExtension < Struct.new(:media_type, :subtype, - :params, :content_id, - :description, :encoding, :size) - def multipart? - return false - end - end - - deprecate_constant :BodyTypeExtension - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_parser/parser_utils.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_parser/parser_utils.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_parser/parser_utils.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_parser/parser_utils.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,240 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - class ResponseParser - # basic utility methods for parsing. - # - # (internal API, subject to change) - module ParserUtils # :nodoc: - - module Generator # :nodoc: - - LOOKAHEAD = "(@token ||= next_token)" - SHIFT_TOKEN = "(@token = nil)" - - # we can skip lexer for single character matches, as a shortcut - def def_char_matchers(name, char, token) - byte = char.ord - match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name - char = char.dump - class_eval <<~RUBY, __FILE__, __LINE__ + 1 - # frozen_string_literal: true - - # force use of #next_token; no string peeking - def lookahead_#{name}? - #{LOOKAHEAD}&.symbol == #{token} - end - - # use token or string peek - def peek_#{name}? - @token ? @token.symbol == #{token} : @str.getbyte(@pos) == #{byte} - end - - # like accept(token_symbols); returns token or nil - def #{name}? - if @token&.symbol == #{token} - #{SHIFT_TOKEN} - #{char} - elsif !@token && @str.getbyte(@pos) == #{byte} - @pos += 1 - #{char} - end - end - - # like match(token_symbols); returns token or raises parse_error - def #{match_name} - if @token&.symbol == #{token} - #{SHIFT_TOKEN} - #{char} - elsif !@token && @str.getbyte(@pos) == #{byte} - @pos += 1 - #{char} - else - parse_error("unexpected %s (expected %p)", - @token&.symbol || @str[@pos].inspect, #{char}) - end - end - RUBY - end - - # TODO: move coersion to the token.value method? - def def_token_matchers(name, *token_symbols, coerce: nil, send: nil) - match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name - - if token_symbols.size == 1 - token = token_symbols.first - matcher = "token&.symbol == %p" % [token] - desc = token - else - matcher = "%p.include? token&.symbol" % [token_symbols] - desc = token_symbols.join(" or ") - end - - value = "(token.value)" - value = coerce.to_s + value if coerce - value = [value, send].join(".") if send - - raise_parse_error = <<~RUBY - parse_error("unexpected %s (expected #{desc})", token&.symbol) - RUBY - - class_eval <<~RUBY, __FILE__, __LINE__ + 1 - # frozen_string_literal: true - - # lookahead version of match, returning the value - def lookahead_#{name}! - token = #{LOOKAHEAD} - if #{matcher} - #{value} - else - #{raise_parse_error} - end - end - - def #{name}? - token = #{LOOKAHEAD} - if #{matcher} - #{SHIFT_TOKEN} - #{value} - end - end - - def #{match_name} - token = #{LOOKAHEAD} - if #{matcher} - #{SHIFT_TOKEN} - #{value} - else - #{raise_parse_error} - end - end - RUBY - end - - end - - private - - # TODO: after checking the lookahead, use a regexp for remaining chars. - # That way a loop isn't needed. - def combine_adjacent(*tokens) - result = "".b - while token = accept(*tokens) - result << token.value - end - if result.empty? - parse_error('unexpected token %s (expected %s)', - lookahead.symbol, tokens.join(" or ")) - end - result - end - - def match(*args) - token = lookahead - unless args.include?(token.symbol) - parse_error('unexpected token %s (expected %s)', - token.symbol.id2name, - args.collect {|i| i.id2name}.join(" or ")) - end - shift_token - token - end - - # like match, but does not raise error on failure. - # - # returns and shifts token on successful match - # returns nil and leaves @token unshifted on no match - def accept(*args) - token = lookahead - if args.include?(token.symbol) - shift_token - token - end - end - - # To be used conditionally: - # assert_no_lookahead if Net::IMAP.debug - def assert_no_lookahead - @token.nil? or - parse_error("assertion failed: expected @token.nil?, actual %s: %p", - @token.symbol, @token.value) - end - - # like accept, without consuming the token - def lookahead?(*symbols) - @token if symbols.include?((@token ||= next_token)&.symbol) - end - - def lookahead - @token ||= next_token - end - - # like match, without consuming the token - def lookahead!(*args) - if args.include?((@token ||= next_token)&.symbol) - @token - else - parse_error('unexpected token %s (expected %s)', - @token&.symbol, args.join(" or ")) - end - end - - def peek_str?(str) - assert_no_lookahead if Net::IMAP.debug - @str[@pos, str.length] == str - end - - def peek_re(re) - assert_no_lookahead if Net::IMAP.debug - re.match(@str, @pos) - end - - def accept_re(re) - assert_no_lookahead if Net::IMAP.debug - re.match(@str, @pos) and @pos = $~.end(0) - $~ - end - - def match_re(re, name) - assert_no_lookahead if Net::IMAP.debug - if re.match(@str, @pos) - @pos = $~.end(0) - $~ - else - parse_error("invalid #{name}") - end - end - - def shift_token - @token = nil - end - - def parse_error(fmt, *args) - msg = format(fmt, *args) - if IMAP.debug - local_path = File.dirname(__dir__) - tok = @token ? "%s: %p" % [@token.symbol, @token.value] : "nil" - warn "%s %s: %s" % [self.class, __method__, msg] - warn " tokenized : %s" % [@str[...@pos].dump] - warn " remaining : %s" % [@str[@pos..].dump] - warn " @lex_state: %s" % [@lex_state] - warn " @pos : %d" % [@pos] - warn " @token : %s" % [tok] - caller_locations(1..20).each_with_index do |cloc, idx| - next unless cloc.path&.start_with?(local_path) - warn " caller[%2d]: %-30s (%s:%d)" % [ - idx, - cloc.base_label, - File.basename(cloc.path, ".rb"), - cloc.lineno - ] - end - end - raise ResponseParseError, msg - end - - end - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_parser.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_parser.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_parser.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/response_parser.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,2120 +0,0 @@ -# frozen_string_literal: true - -require_relative "errors" -require_relative "response_parser/parser_utils" - -module Net - class IMAP < Protocol - - # Parses an \IMAP server response. - class ResponseParser - include ParserUtils - extend ParserUtils::Generator - - # :call-seq: Net::IMAP::ResponseParser.new -> Net::IMAP::ResponseParser - def initialize - @str = nil - @pos = nil - @lex_state = nil - @token = nil - end - - # :call-seq: - # parse(str) -> ContinuationRequest - # parse(str) -> UntaggedResponse - # parse(str) -> TaggedResponse - # - # Raises ResponseParseError for unparsable strings. - def parse(str) - @str = str - @pos = 0 - @lex_state = EXPR_BEG - @token = nil - return response - end - - private - - # :stopdoc: - - EXPR_BEG = :EXPR_BEG # the default, used in most places - EXPR_DATA = :EXPR_DATA # envelope, body(structure), namespaces - - T_SPACE = :SPACE # atom special - T_ATOM = :ATOM # atom (subset of astring chars) - T_NIL = :NIL # subset of atom and label - T_NUMBER = :NUMBER # subset of atom - T_LBRA = :LBRA # subset of atom - T_PLUS = :PLUS # subset of atom; tag special - T_RBRA = :RBRA # atom special; resp_special; valid astring char - T_QUOTED = :QUOTED # starts/end with atom special - T_BSLASH = :BSLASH # atom special; quoted special - T_LPAR = :LPAR # atom special; paren list delimiter - T_RPAR = :RPAR # atom special; paren list delimiter - T_STAR = :STAR # atom special; list wildcard - T_PERCENT = :PERCENT # atom special; list wildcard - T_LITERAL = :LITERAL # starts with atom special - T_LITERAL8 = :LITERAL8 # starts with atom char "~" - T_CRLF = :CRLF # atom special; text special; quoted special - T_TEXT = :TEXT # any char except CRLF - T_EOF = :EOF # end of response string - - module ResponseConditions - OK = "OK" - NO = "NO" - BAD = "BAD" - BYE = "BYE" - PREAUTH = "PREAUTH" - - RESP_COND_STATES = [OK, NO, BAD ].freeze - RESP_DATA_CONDS = [OK, NO, BAD, BYE, ].freeze - AUTH_CONDS = [OK, PREAUTH].freeze - GREETING_CONDS = [OK, BYE, PREAUTH].freeze - RESP_CONDS = [OK, NO, BAD, BYE, PREAUTH].freeze - end - include ResponseConditions - - module Patterns - - module CharClassSubtraction - refine Regexp do - def -(rhs); /[#{source}&&[^#{rhs.source}]]/n.freeze end - end - end - using CharClassSubtraction - - # From RFC5234, "Augmented BNF for Syntax Specifications: ABNF" - # >>> - # ALPHA = %x41-5A / %x61-7A ; A-Z / a-z - # CHAR = %x01-7F - # CRLF = CR LF - # ; Internet standard newline - # CTL = %x00-1F / %x7F - # ; controls - # DIGIT = %x30-39 - # ; 0-9 - # DQUOTE = %x22 - # ; " (Double Quote) - # HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" - # OCTET = %x00-FF - # SP = %x20 - module RFC5234 - ALPHA = /[A-Za-z]/n - CHAR = /[\x01-\x7f]/n - CRLF = /\r\n/n - CTL = /[\x00-\x1F\x7F]/n - DIGIT = /\d/n - DQUOTE = /"/n - HEXDIG = /\h/ - OCTET = /[\x00-\xFF]/n # not using /./m for embedding purposes - SP = / /n - end - - # UTF-8, a transformation format of ISO 10646 - # >>> - # UTF8-1 = %x00-7F - # UTF8-tail = %x80-BF - # UTF8-2 = %xC2-DF UTF8-tail - # UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / - # %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) - # UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / - # %xF4 %x80-8F 2( UTF8-tail ) - # UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 - # UTF8-octets = *( UTF8-char ) - # - # n.b. String * Integer is used for repetition, rather than /x{3}/, - # because ruby 3.2's linear-time cache-based optimization doesn't work - # with "bounded or fixed times repetition nesting in another repetition - # (e.g. /(a{2,3})*/). It is an implementation issue entirely, but we - # believe it is hard to support this case correctly." - # See https://bugs.ruby-lang.org/issues/19104 - module RFC3629 - UTF8_1 = /[\x00-\x7f]/n # aka ASCII 7bit - UTF8_TAIL = /[\x80-\xBF]/n - UTF8_2 = /[\xC2-\xDF]#{UTF8_TAIL}/n - UTF8_3 = Regexp.union(/\xE0[\xA0-\xBF]#{UTF8_TAIL}/n, - /\xED[\x80-\x9F]#{UTF8_TAIL}/n, - /[\xE1-\xEC]#{ UTF8_TAIL.source * 2}/n, - /[\xEE-\xEF]#{ UTF8_TAIL.source * 2}/n) - UTF8_4 = Regexp.union(/[\xF1-\xF3]#{ UTF8_TAIL.source * 3}/n, - /\xF0[\x90-\xBF]#{UTF8_TAIL.source * 2}/n, - /\xF4[\x80-\x8F]#{UTF8_TAIL.source * 2}/n) - UTF8_CHAR = Regexp.union(UTF8_1, UTF8_2, UTF8_3, UTF8_4) - UTF8_OCTETS = /#{UTF8_CHAR}*/n - end - - include RFC5234 - include RFC3629 - - # CHAR8 = %x01-ff - # ; any OCTET except NUL, %x00 - CHAR8 = /[\x01-\xff]/n - - # list-wildcards = "%" / "*" - LIST_WILDCARDS = /[%*]/n - # quoted-specials = DQUOTE / "\" - QUOTED_SPECIALS = /["\\]/n - # resp-specials = "]" - RESP_SPECIALS = /[\]]/n - - # atomish = 1* - # ; We use "atomish" for msg-att and section, in order - # ; to simplify "BODY[HEADER.FIELDS (foo bar)]". - # - # atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / - # quoted-specials / resp-specials - # ATOM-CHAR = - # atom = 1*ATOM-CHAR - # ASTRING-CHAR = ATOM-CHAR / resp-specials - # tag = 1* - - ATOM_SPECIALS = /[(){ \x00-\x1f\x7f%*"\\\]]/n - ASTRING_SPECIALS = /[(){ \x00-\x1f\x7f%*"\\]/n - - ASTRING_CHAR = CHAR - ASTRING_SPECIALS - ATOM_CHAR = CHAR - ATOM_SPECIALS - - ATOM = /#{ATOM_CHAR}+/n - ASTRING_CHARS = /#{ASTRING_CHAR}+/n - ATOMISH = /#{ATOM_CHAR - /[\[]/ }+/ - TAG = /#{ASTRING_CHAR - /[+]/ }+/ - - # TEXT-CHAR = - TEXT_CHAR = CHAR - /[\r\n]/ - - # resp-text-code = ... / atom [SP 1*] - CODE_TEXT_CHAR = TEXT_CHAR - RESP_SPECIALS - CODE_TEXT = /#{CODE_TEXT_CHAR}+/n - - # flag = "\Answered" / "\Flagged" / "\Deleted" / - # "\Seen" / "\Draft" / flag-keyword / flag-extension - # ; Does not include "\Recent" - # flag-extension = "\" atom - # ; Future expansion. Client implementations - # ; MUST accept flag-extension flags. Server - # ; implementations MUST NOT generate - # ; flag-extension flags except as defined by - # ; a future Standard or Standards Track - # ; revisions of this specification. - # flag-keyword = "$MDNSent" / "$Forwarded" / "$Junk" / - # "$NotJunk" / "$Phishing" / atom - # - # flag-perm = flag / "\*" - # - # Not checking for max one mbx-list-sflag in the parser. - # >>> - # mbx-list-oflag = "\Noinferiors" / child-mbox-flag / - # "\Subscribed" / "\Remote" / flag-extension - # ; Other flags; multiple from this list are - # ; possible per LIST response, but each flag - # ; can only appear once per LIST response - # mbx-list-sflag = "\NonExistent" / "\Noselect" / "\Marked" / - # "\Unmarked" - # ; Selectability flags; only one per LIST response - # child-mbox-flag = "\HasChildren" / "\HasNoChildren" - # ; attributes for the CHILDREN return option, at most - # ; one possible per LIST response - FLAG = /\\?#{ATOM}/n - FLAG_EXTENSION = /\\#{ATOM}/n - FLAG_KEYWORD = ATOM - FLAG_PERM = Regexp.union(FLAG, "\\*") - MBX_FLAG = FLAG_EXTENSION - - # flag-list = "(" [flag *(SP flag)] ")" - # resp-text-code =/ "PERMANENTFLAGS" SP - # "(" [flag-perm *(SP flag-perm)] ")" - # mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag - # *(SP mbx-list-oflag) / - # mbx-list-oflag *(SP mbx-list-oflag) - # (Not checking for max one mbx-list-sflag in the parser.) - FLAG_LIST = /\G\((#{FLAG }(?:#{SP}#{FLAG })*|)\)/ni - FLAG_PERM_LIST = /\G\((#{FLAG_PERM}(?:#{SP}#{FLAG_PERM})*|)\)/ni - MBX_LIST_FLAGS = /\G (#{MBX_FLAG }(?:#{SP}#{MBX_FLAG })*) /nix - - # Gmail allows SP and "]" in flags....... - QUIRKY_FLAG = Regexp.union(/\\?#{ASTRING_CHARS}/n, "\\*") - QUIRKY_FLAGS_LIST = /\G\(( [^)]* )\)/nx - - # RFC3501: - # QUOTED-CHAR = / - # "\" quoted-specials - # RFC9051: - # QUOTED-CHAR = / - # "\" quoted-specials / UTF8-2 / UTF8-3 / UTF8-4 - # RFC3501 & RFC9051: - # quoted = DQUOTE *QUOTED-CHAR DQUOTE - QUOTED_CHAR_safe = TEXT_CHAR - QUOTED_SPECIALS - QUOTED_CHAR_esc = /\\#{QUOTED_SPECIALS}/n - QUOTED_CHAR_rev1 = Regexp.union(QUOTED_CHAR_safe, QUOTED_CHAR_esc) - QUOTED_CHAR_rev2 = Regexp.union(QUOTED_CHAR_rev1, - UTF8_2, UTF8_3, UTF8_4) - QUOTED_rev1 = /"(#{QUOTED_CHAR_rev1}*)"/n - QUOTED_rev2 = /"(#{QUOTED_CHAR_rev2}*)"/n - - # RFC3501: - # text = 1*TEXT-CHAR - # RFC9051: - # text = 1*(TEXT-CHAR / UTF8-2 / UTF8-3 / UTF8-4) - # ; Non-ASCII text can only be returned - # ; after ENABLE IMAP4rev2 command - TEXT_rev1 = /#{TEXT_CHAR}+/ - TEXT_rev2 = /#{Regexp.union TEXT_CHAR, UTF8_2, UTF8_3, UTF8_4}+/ - - # tagged-label-fchar = ALPHA / "-" / "_" / "." - TAGGED_LABEL_FCHAR = /[a-zA-Z\-_.]/n - # tagged-label-char = tagged-label-fchar / DIGIT / ":" - TAGGED_LABEL_CHAR = /[a-zA-Z\-_.0-9:]*/n - # tagged-ext-label = tagged-label-fchar *tagged-label-char - # ; Is a valid RFC 3501 "atom". - TAGGED_EXT_LABEL = /#{TAGGED_LABEL_FCHAR}#{TAGGED_LABEL_CHAR}*/n - - # nz-number = digit-nz *DIGIT - # ; Non-zero unsigned 32-bit integer - # ; (0 < n < 4,294,967,296) - NZ_NUMBER = /[1-9]\d*/n - - # seq-number = nz-number / "*" - # ; message sequence number (COPY, FETCH, STORE - # ; commands) or unique identifier (UID COPY, - # ; UID FETCH, UID STORE commands). - # ; * represents the largest number in use. In - # ; the case of message sequence numbers, it is - # ; the number of messages in a non-empty mailbox. - # ; In the case of unique identifiers, it is the - # ; unique identifier of the last message in the - # ; mailbox or, if the mailbox is empty, the - # ; mailbox's current UIDNEXT value. - # ; The server should respond with a tagged BAD - # ; response to a command that uses a message - # ; sequence number greater than the number of - # ; messages in the selected mailbox. This - # ; includes "*" if the selected mailbox is empty. - SEQ_NUMBER = /#{NZ_NUMBER}|\*/n - - # seq-range = seq-number ":" seq-number - # ; two seq-number values and all values between - # ; these two regardless of order. - # ; Example: 2:4 and 4:2 are equivalent and - # ; indicate values 2, 3, and 4. - # ; Example: a unique identifier sequence range of - # ; 3291:* includes the UID of the last message in - # ; the mailbox, even if that value is less than - # ; 3291. - SEQ_RANGE = /#{SEQ_NUMBER}:#{SEQ_NUMBER}/n - - # sequence-set = (seq-number / seq-range) ["," sequence-set] - # ; set of seq-number values, regardless of order. - # ; Servers MAY coalesce overlaps and/or execute - # ; the sequence in any order. - # ; Example: a message sequence number set of - # ; 2,4:7,9,12:* for a mailbox with 15 messages is - # ; equivalent to 2,4,5,6,7,9,12,13,14,15 - # ; Example: a message sequence number set of - # ; *:4,5:7 for a mailbox with 10 messages is - # ; equivalent to 10,9,8,7,6,5,4,5,6,7 and MAY - # ; be reordered and overlap coalesced to be - # ; 4,5,6,7,8,9,10. - SEQUENCE_SET_ITEM = /#{SEQ_NUMBER}|#{SEQ_RANGE}/n - SEQUENCE_SET = /#{SEQUENCE_SET_ITEM}(?:,#{SEQUENCE_SET_ITEM})*/n - SEQUENCE_SET_STR = /\A#{SEQUENCE_SET}\z/n - - # RFC3501: - # literal = "{" number "}" CRLF *CHAR8 - # ; Number represents the number of CHAR8s - # RFC9051: - # literal = "{" number64 ["+"] "}" CRLF *CHAR8 - # ; represents the number of CHAR8s. - # ; A non-synchronizing literal is distinguished - # ; from a synchronizing literal by the presence of - # ; "+" before the closing "}". - # ; Non-synchronizing literals are not allowed when - # ; sent from server to the client. - LITERAL = /\{(\d+)\}\r\n/n - - # RFC3516 (BINARY): - # literal8 = "~{" number "}" CRLF *OCTET - # ; represents the number of OCTETs - # ; in the response string. - # RFC9051: - # literal8 = "~{" number64 "}" CRLF *OCTET - # ; represents the number of OCTETs - # ; in the response string. - LITERAL8 = /~\{(\d+)\}\r\n/n - - module_function - - def unescape_quoted!(quoted) - quoted - &.gsub!(/\\(#{QUOTED_SPECIALS})/n, "\\1") - &.force_encoding("UTF-8") - end - - def unescape_quoted(quoted) - quoted - &.gsub(/\\(#{QUOTED_SPECIALS})/n, "\\1") - &.force_encoding("UTF-8") - end - - end - - # the default, used in most places - BEG_REGEXP = /\G(?:\ -(?# 1: SPACE )( )|\ -(?# 2: LITERAL8)#{Patterns::LITERAL8}|\ -(?# 3: ATOM prefixed with a compatible subtype)\ -((?:\ -(?# 4: NIL )(NIL)|\ -(?# 5: NUMBER )(\d+)|\ -(?# 6: PLUS )(\+))\ -(?# 7: ATOM remaining after prefix )(#{Patterns::ATOMISH})?\ -(?# This enables greedy alternation without lookahead, in linear time.)\ -)|\ -(?# Also need to check for ATOM without a subtype prefix.)\ -(?# 8: ATOM )(#{Patterns::ATOMISH})|\ -(?# 9: QUOTED )#{Patterns::QUOTED_rev2}|\ -(?# 10: LPAR )(\()|\ -(?# 11: RPAR )(\))|\ -(?# 12: BSLASH )(\\)|\ -(?# 13: STAR )(\*)|\ -(?# 14: LBRA )(\[)|\ -(?# 15: RBRA )(\])|\ -(?# 16: LITERAL )#{Patterns::LITERAL}|\ -(?# 17: PERCENT )(%)|\ -(?# 18: CRLF )(\r\n)|\ -(?# 19: EOF )(\z))/ni - - # envelope, body(structure), namespaces - DATA_REGEXP = /\G(?:\ -(?# 1: SPACE )( )|\ -(?# 2: NIL )(NIL)|\ -(?# 3: NUMBER )(\d+)|\ -(?# 4: QUOTED )#{Patterns::QUOTED_rev2}|\ -(?# 5: LITERAL )#{Patterns::LITERAL}|\ -(?# 6: LPAR )(\()|\ -(?# 7: RPAR )(\)))/ni - - # text, after 'resp-text-code "]"' - TEXT_REGEXP = /\G(#{Patterns::TEXT_rev2})/n - - # resp-text-code, after 'atom SP' - CTEXT_REGEXP = /\G(#{Patterns::CODE_TEXT})/n - - Token = Struct.new(:symbol, :value) - - def_char_matchers :SP, " ", :T_SPACE - def_char_matchers :PLUS, "+", :T_PLUS - def_char_matchers :STAR, "*", :T_STAR - - def_char_matchers :lpar, "(", :T_LPAR - def_char_matchers :rpar, ")", :T_RPAR - - def_char_matchers :lbra, "[", :T_LBRA - def_char_matchers :rbra, "]", :T_RBRA - - # valid number ranges are not enforced by parser - # number = 1*DIGIT - # ; Unsigned 32-bit integer - # ; (0 <= n < 4,294,967,296) - def_token_matchers :number, T_NUMBER, coerce: Integer - - def_token_matchers :quoted, T_QUOTED - - # string = quoted / literal - def_token_matchers :string, T_QUOTED, T_LITERAL - - # used by nstring8 = nstring / literal8 - def_token_matchers :string8, T_QUOTED, T_LITERAL, T_LITERAL8 - - # use where string represents "LABEL" values - def_token_matchers :case_insensitive__string, - T_QUOTED, T_LITERAL, - send: :upcase - - # n.b: NIL? and NIL! return the "NIL" atom string (truthy) on success. - # NIL? returns nil when it does *not* match - def_token_matchers :NIL, T_NIL - - # In addition to explicitly uses of +tagged-ext-label+, use this to match - # keywords when the grammar has not provided any extension syntax. - # - # Do *not* use this for labels where the grammar specifies extensions - # can be +atom+, even if all currently defined labels would match. For - # example response codes in +resp-text-code+. - # - # tagged-ext-label = tagged-label-fchar *tagged-label-char - # ; Is a valid RFC 3501 "atom". - # tagged-label-fchar = ALPHA / "-" / "_" / "." - # tagged-label-char = tagged-label-fchar / DIGIT / ":" - # - # TODO: add to lexer and only match tagged-ext-label - def_token_matchers :tagged_ext_label, T_ATOM, T_NIL, send: :upcase - - def_token_matchers :CRLF, T_CRLF - def_token_matchers :EOF, T_EOF - - # atom = 1*ATOM-CHAR - # ATOM-CHAR = - ATOM_TOKENS = [T_ATOM, T_NUMBER, T_NIL, T_LBRA, T_PLUS] - - SEQUENCE_SET_TOKENS = [T_ATOM, T_NUMBER, T_STAR] - - # sequence-set = (seq-number / seq-range) ["," sequence-set] - # sequence-set =/ seq-last-command - # ; Allow for "result of the last command" - # ; indicator. - # seq-last-command = "$" - # - # *note*: doesn't match seq-last-command - def sequence_set - str = combine_adjacent(*SEQUENCE_SET_TOKENS) - if Patterns::SEQUENCE_SET_STR.match?(str) - SequenceSet[str] - else - parse_error("unexpected atom %p, expected sequence-set", str) - end - end - - # ASTRING-CHAR = ATOM-CHAR / resp-specials - # resp-specials = "]" - ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA].freeze - - ASTRING_TOKENS = [T_QUOTED, *ASTRING_CHARS_TOKENS, T_LITERAL].freeze - - # tag = 1* - TAG_TOKENS = (ASTRING_CHARS_TOKENS - [T_PLUS]).freeze - - # TODO: handle atom, astring_chars, and tag entirely inside the lexer - def atom; combine_adjacent(*ATOM_TOKENS) end - def astring_chars; combine_adjacent(*ASTRING_CHARS_TOKENS) end - def tag; combine_adjacent(*TAG_TOKENS) end - - # the #accept version of #atom - def atom?; -combine_adjacent(*ATOM_TOKENS) if lookahead?(*ATOM_TOKENS) end - - # Returns atom.upcase - def case_insensitive__atom; -combine_adjacent(*ATOM_TOKENS).upcase end - - # Returns atom?&.upcase - def case_insensitive__atom? - -combine_adjacent(*ATOM_TOKENS).upcase if lookahead?(*ATOM_TOKENS) - end - - # astring = 1*ASTRING-CHAR / string - def astring - lookahead?(*ASTRING_CHARS_TOKENS) ? astring_chars : string - end - - def astring? - lookahead?(*ASTRING_CHARS_TOKENS) ? astring_chars : string? - end - - # Use #label or #label_in to assert specific known labels - # (+tagged-ext-label+ only, not +atom+). - def label(word) - (val = tagged_ext_label) == word and return val - parse_error("unexpected atom %p, expected %p instead", val, word) - end - - # Use #label or #label_in to assert specific known labels - # (+tagged-ext-label+ only, not +atom+). - def label_in(*labels) - lbl = tagged_ext_label and labels.include?(lbl) and return lbl - parse_error("unexpected atom %p, expected one of %s instead", - lbl, labels.join(" or ")) - end - - # expects "OK" or "PREAUTH" and raises InvalidResponseError on failure - def resp_cond_auth__name - lbl = tagged_ext_label and AUTH_CONDS.include? lbl and return lbl - raise InvalidResponseError, "bad response type %p, expected %s" % [ - lbl, AUTH_CONDS.join(" or ") - ] - end - - # expects "OK" or "NO" or "BAD" and raises InvalidResponseError on failure - def resp_cond_state__name - lbl = tagged_ext_label and RESP_COND_STATES.include? lbl and return lbl - raise InvalidResponseError, "bad response type %p, expected %s" % [ - lbl, RESP_COND_STATES.join(" or ") - ] - end - - # nstring = string / nil - def nstring - NIL? ? nil : string - end - - def nstring8 - NIL? ? nil : string8 - end - - def nquoted - NIL? ? nil : quoted - end - - # use where nstring represents "LABEL" values - def case_insensitive__nstring - NIL? ? nil : case_insensitive__string - end - - # tagged-ext-comp = astring / - # tagged-ext-comp *(SP tagged-ext-comp) / - # "(" tagged-ext-comp ")" - # ; Extensions that follow this general - # ; syntax should use nstring instead of - # ; astring when appropriate in the context - # ; of the extension. - # ; Note that a message set or a "number" - # ; can always be represented as an "atom". - # ; A URL should be represented as - # ; a "quoted" string. - def tagged_ext_comp - vals = [] - while true - vals << case lookahead!(*ASTRING_TOKENS, T_LPAR).symbol - when T_LPAR then lpar; ary = tagged_ext_comp; rpar; ary - when T_NUMBER then number - else astring - end - SP? or break - end - vals - end - - # tagged-ext-simple is a subset of atom - # TODO: recognize sequence-set in the lexer - # - # tagged-ext-simple = sequence-set / number / number64 - def tagged_ext_simple - number? || sequence_set - end - - # tagged-ext-val = tagged-ext-simple / - # "(" [tagged-ext-comp] ")" - def tagged_ext_val - if lpar? - _ = peek_rpar? ? [] : tagged_ext_comp - rpar - _ - else - tagged_ext_simple - end - end - - # mailbox = "INBOX" / astring - # ; INBOX is case-insensitive. All case variants of - # ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX - # ; not as an astring. An astring which consists of - # ; the case-insensitive sequence "I" "N" "B" "O" "X" - # ; is considered to be INBOX and not an astring. - # ; Refer to section 5.1 for further - # ; semantic details of mailbox names. - alias mailbox astring - - # valid number ranges are not enforced by parser - # number64 = 1*DIGIT - # ; Unsigned 63-bit integer - # ; (0 <= n <= 9,223,372,036,854,775,807) - alias number64 number - alias number64? number? - - # valid number ranges are not enforced by parser - # nz-number = digit-nz *DIGIT - # ; Non-zero unsigned 32-bit integer - # ; (0 < n < 4,294,967,296) - alias nz_number number - alias nz_number? number? - - # valid number ranges are not enforced by parser - # nz-number64 = digit-nz *DIGIT - # ; Unsigned 63-bit integer - # ; (0 < n <= 9,223,372,036,854,775,807) - alias nz_number64 nz_number - - # valid number ranges are not enforced by parser - # uniqueid = nz-number - # ; Strictly ascending - alias uniqueid nz_number - - # valid number ranges are not enforced by parser - # - # a 64-bit unsigned integer and is the decimal equivalent for the ID hex - # string used in the web interface and the Gmail API. - alias x_gm_id number - - # [RFC3501 & RFC9051:] - # response = *(continue-req / response-data) response-done - # - # For simplicity, response isn't interpreted as the combination of the - # three response types, but instead represents any individual server - # response. Our simplified interpretation is defined as: - # response = continue-req | response_data | response-tagged - # - # n.b: our "response-tagged" definition parses "greeting" too. - def response - resp = case lookahead!(T_PLUS, T_STAR, *TAG_TOKENS).symbol - when T_PLUS then continue_req - when T_STAR then response_data - else response_tagged - end - accept_spaces # QUIRKY: Ignore trailing space (MS Exchange Server?) - CRLF! - EOF! - resp - end - - # RFC3501 & RFC9051: - # continue-req = "+" SP (resp-text / base64) CRLF - # - # n.b: base64 is valid resp-text. And in the spirit of RFC9051 Appx E 23 - # (and to workaround existing servers), we use the following grammar: - # - # continue-req = "+" (SP (resp-text)) CRLF - def continue_req - PLUS! - ContinuationRequest.new(SP? ? resp_text : ResponseText::EMPTY, @str) - end - - RE_RESPONSE_TYPE = /\G(?:\d+ )?(?#{Patterns::TAGGED_EXT_LABEL})/n - - # [RFC3501:] - # response-data = "*" SP (resp-cond-state / resp-cond-bye / - # mailbox-data / message-data / capability-data) CRLF - # [RFC4466:] - # response-data = "*" SP response-payload CRLF - # response-payload = resp-cond-state / resp-cond-bye / - # mailbox-data / message-data / capability-data - # RFC5161 (ENABLE capability): - # response-data =/ "*" SP enable-data CRLF - # RFC5255 (LANGUAGE capability) - # response-payload =/ language-data - # RFC5255 (I18NLEVEL=1 and I18NLEVEL=2 capabilities) - # response-payload =/ comparator-data - # [RFC9051:] - # response-data = "*" SP (resp-cond-state / resp-cond-bye / - # mailbox-data / message-data / capability-data / - # enable-data) CRLF - # - # [merging in greeting and response-fatal:] - # greeting = "*" SP (resp-cond-auth / resp-cond-bye) CRLF - # response-fatal = "*" SP resp-cond-bye CRLF - # response-data =/ "*" SP (resp-cond-auth / resp-cond-bye) CRLF - # [removing duplicates, this is simply] - # response-payload =/ resp-cond-auth - # - # TODO: remove resp-cond-auth and handle greeting separately - def response_data - STAR!; SP! - m = peek_re(RE_RESPONSE_TYPE) or parse_error("unparsable response") - case m["type"].upcase - when "OK" then resp_cond_state__untagged # RFC3501, RFC9051 - when "FETCH" then message_data__fetch # RFC3501, RFC9051 - when "EXPUNGE" then message_data__expunge # RFC3501, RFC9051 - when "EXISTS" then mailbox_data__exists # RFC3501, RFC9051 - when "ESEARCH" then esearch_response # RFC4731, RFC9051, etc - when "VANISHED" then expunged_resp # RFC7162 - when "UIDFETCH" then uidfetch_resp # (draft) UIDONLY - when "SEARCH" then mailbox_data__search # RFC3501 (obsolete) - when "CAPABILITY" then capability_data__untagged # RFC3501, RFC9051 - when "FLAGS" then mailbox_data__flags # RFC3501, RFC9051 - when "LIST" then mailbox_data__list # RFC3501, RFC9051 - when "STATUS" then mailbox_data__status # RFC3501, RFC9051 - when "NAMESPACE" then namespace_response # RFC2342, RFC9051 - when "ENABLED" then enable_data # RFC5161, RFC9051 - when "BAD" then resp_cond_state__untagged # RFC3501, RFC9051 - when "NO" then resp_cond_state__untagged # RFC3501, RFC9051 - when "PREAUTH" then resp_cond_auth # RFC3501, RFC9051 - when "BYE" then resp_cond_bye # RFC3501, RFC9051 - when "RECENT" then mailbox_data__recent # RFC3501 (obsolete) - when "SORT" then sort_data # RFC5256, RFC7162 - when "THREAD" then thread_data # RFC5256 - when "QUOTA" then quota_response # RFC2087, RFC9208 - when "QUOTAROOT" then quotaroot_response # RFC2087, RFC9208 - when "ID" then id_response # RFC2971 - when "ACL" then acl_data # RFC4314 - when "LISTRIGHTS" then listrights_data # RFC4314 - when "MYRIGHTS" then myrights_data # RFC4314 - when "METADATA" then metadata_resp # RFC5464 - when "LANGUAGE" then language_data # RFC5255 - when "COMPARATOR" then comparator_data # RFC5255 - when "CONVERTED" then message_data__converted # RFC5259 - when "LSUB" then mailbox_data__lsub # RFC3501 (obsolete) - when "XLIST" then mailbox_data__xlist # deprecated - when "NOOP" then response_data__noop - else response_data__unhandled - end - end - - def response_data__unhandled(klass = UntaggedResponse) - num = number?; SP? - type = tagged_ext_label; SP? - text = remaining_unparsed - data = - if num && text then UnparsedNumericResponseData.new(num, text) - elsif text then UnparsedData.new(text) - else num - end - klass.new(type, data, @str) - end - - # reads all the way up until CRLF - def remaining_unparsed - str = @str[@pos...-2] and @pos += str.bytesize - str&.empty? ? nil : str - end - - def response_data__ignored; response_data__unhandled(IgnoredResponse) end - alias response_data__noop response_data__ignored - - alias esearch_response response_data__unhandled - alias expunged_resp response_data__unhandled - alias uidfetch_resp response_data__unhandled - alias listrights_data response_data__unhandled - alias myrights_data response_data__unhandled - alias metadata_resp response_data__unhandled - alias language_data response_data__unhandled - alias comparator_data response_data__unhandled - alias message_data__converted response_data__unhandled - - # RFC3501 & RFC9051: - # response-tagged = tag SP resp-cond-state CRLF - def response_tagged - TaggedResponse.new(tag, *(SP!; resp_cond_state), @str) - end - - # RFC3501 & RFC9051: - # resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text - # - # NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing - # servers), we don't require a final SP and instead parse this as: - # - # resp-cond-state = ("OK" / "NO" / "BAD") [SP resp-text] - def resp_cond_state - [resp_cond_state__name, SP? ? resp_text : ResponseText::EMPTY] - end - - def resp_cond_state__untagged - UntaggedResponse.new(*resp_cond_state, @str) - end - - # resp-cond-auth = ("OK" / "PREAUTH") SP resp-text - # - # NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing - # servers), we don't require a final SP and instead parse this as: - # - # resp-cond-auth = ("OK" / "PREAUTH") [SP resp-text] - def resp_cond_auth - UntaggedResponse.new(resp_cond_auth__name, - SP? ? resp_text : ResponseText::EMPTY, - @str) - end - - # resp-cond-bye = "BYE" SP resp-text - # - # NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing - # servers), we don't require a final SP and instead parse this as: - # - # resp-cond-bye = "BYE" [SP resp-text] - def resp_cond_bye - UntaggedResponse.new(label(BYE), - SP? ? resp_text : ResponseText::EMPTY, - @str) - end - - # message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att)) - def message_data__fetch - seq = nz_number; SP! - name = label "FETCH"; SP! - data = FetchData.new(seq, msg_att(seq)) - UntaggedResponse.new(name, data, @str) - end - - def response_data__simple_numeric - data = nz_number; SP! - name = tagged_ext_label - UntaggedResponse.new(name, data, @str) - end - - alias message_data__expunge response_data__simple_numeric - alias mailbox_data__exists response_data__simple_numeric - alias mailbox_data__recent response_data__simple_numeric - - # RFC3501 & RFC9051: - # msg-att = "(" (msg-att-dynamic / msg-att-static) - # *(SP (msg-att-dynamic / msg-att-static)) ")" - # - # msg-att-dynamic = "FLAGS" SP "(" [flag-fetch *(SP flag-fetch)] ")" - # RFC5257 (ANNOTATE extension): - # msg-att-dynamic =/ "ANNOTATION" SP - # ( "(" entry-att *(SP entry-att) ")" / - # "(" entry *(SP entry) ")" ) - # RFC7162 (CONDSTORE extension): - # msg-att-dynamic =/ fetch-mod-resp - # fetch-mod-resp = "MODSEQ" SP "(" permsg-modsequence ")" - # RFC8970 (PREVIEW extension): - # msg-att-dynamic =/ "PREVIEW" SP nstring - # - # RFC3501: - # msg-att-static = "ENVELOPE" SP envelope / - # "INTERNALDATE" SP date-time / - # "RFC822" [".HEADER" / ".TEXT"] SP nstring / - # "RFC822.SIZE" SP number / - # "BODY" ["STRUCTURE"] SP body / - # "BODY" section ["<" number ">"] SP nstring / - # "UID" SP uniqueid - # RFC3516 (BINARY extension): - # msg-att-static =/ "BINARY" section-binary SP (nstring / literal8) - # / "BINARY.SIZE" section-binary SP number - # RFC8514 (SAVEDATE extension): - # msg-att-static =/ "SAVEDATE" SP (date-time / nil) - # RFC8474 (OBJECTID extension): - # msg-att-static =/ fetch-emailid-resp / fetch-threadid-resp - # fetch-emailid-resp = "EMAILID" SP "(" objectid ")" - # fetch-threadid-resp = "THREADID" SP ( "(" objectid ")" / nil ) - # RFC9051: - # msg-att-static = "ENVELOPE" SP envelope / - # "INTERNALDATE" SP date-time / - # "RFC822.SIZE" SP number64 / - # "BODY" ["STRUCTURE"] SP body / - # "BODY" section ["<" number ">"] SP nstring / - # "BINARY" section-binary SP (nstring / literal8) / - # "BINARY.SIZE" section-binary SP number / - # "UID" SP uniqueid - # - # Re https://www.rfc-editor.org/errata/eid7246, I'm adding "offset" to the - # official "BINARY" ABNF, like so: - # - # msg-att-static =/ "BINARY" section-binary ["<" number ">"] SP - # (nstring / literal8) - def msg_att(n) - lpar - attr = {} - while true - name = msg_att__label; SP! - val = - case name - when "UID" then uniqueid - when "FLAGS" then flag_list - when "BODY" then body - when /\ABODY\[/ni then nstring - when "BODYSTRUCTURE" then body - when "ENVELOPE" then envelope - when "INTERNALDATE" then date_time - when "RFC822.SIZE" then number64 - when /\ABINARY\[/ni then nstring8 # BINARY, IMAP4rev2 - when /\ABINARY\.SIZE\[/ni then number # BINARY, IMAP4rev2 - when "RFC822" then nstring # not in rev2 - when "RFC822.HEADER" then nstring # not in rev2 - when "RFC822.TEXT" then nstring # not in rev2 - when "MODSEQ" then parens__modseq # CONDSTORE - when "EMAILID" then parens__objectid # OBJECTID - when "THREADID" then nparens__objectid # OBJECTID - when "X-GM-MSGID" then x_gm_id # GMail - when "X-GM-THRID" then x_gm_id # GMail - when "X-GM-LABELS" then x_gm_labels # GMail - else parse_error("unknown attribute `%s' for {%d}", name, n) - end - attr[name] = val - break unless SP? - break if lookahead_rpar? - end - rpar - attr - end - - # appends "[section]" and "" to the base label - def msg_att__label - case (name = tagged_ext_label) - when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni - # ignoring "[]" fixes https://bugs.ruby-lang.org/issues/5620 - lbra? and rbra - when "BODY" - peek_lbra? and name << section and - peek_str?("<") and name << gt__number__lt # partial - when "BINARY", "BINARY.SIZE" - name << section_binary - # see https://www.rfc-editor.org/errata/eid7246 and the note above - peek_str?("<") and name << gt__number__lt # partial - end - name - end - - # this represents the partial size for BODY or BINARY - alias gt__number__lt atom - - # RFC3501 & RFC9051: - # envelope = "(" env-date SP env-subject SP env-from SP - # env-sender SP env-reply-to SP env-to SP env-cc SP - # env-bcc SP env-in-reply-to SP env-message-id ")" - def envelope - @lex_state = EXPR_DATA - lpar; date = env_date - SP!; subject = env_subject - SP!; from = env_from - SP!; sender = env_sender - SP!; reply_to = env_reply_to - SP!; to = env_to - SP!; cc = env_cc - SP!; bcc = env_bcc - SP!; in_reply_to = env_in_reply_to - SP!; message_id = env_message_id - rpar - Envelope.new(date, subject, from, sender, reply_to, - to, cc, bcc, in_reply_to, message_id) - ensure - @lex_state = EXPR_BEG - end - - # env-date = nstring - # env-subject = nstring - # env-in-reply-to = nstring - # env-message-id = nstring - alias env_date nstring - alias env_subject nstring - alias env_in_reply_to nstring - alias env_message_id nstring - - # env-from = "(" 1*address ")" / nil - # env-sender = "(" 1*address ")" / nil - # env-reply-to = "(" 1*address ")" / nil - # env-to = "(" 1*address ")" / nil - # env-cc = "(" 1*address ")" / nil - # env-bcc = "(" 1*address ")" / nil - def nlist__address - return if NIL? - lpar; list = [address]; list << address until (quirky_SP?; rpar?) - list - end - - alias env_from nlist__address - alias env_sender nlist__address - alias env_reply_to nlist__address - alias env_to nlist__address - alias env_cc nlist__address - alias env_bcc nlist__address - - # Used when servers erroneously send an extra SP. - # - # As of 2023-11-28, Outlook.com (still) sends SP - # between +address+ in env-* lists. - alias quirky_SP? SP? - - # date-time = DQUOTE date-day-fixed "-" date-month "-" date-year - # SP time SP zone DQUOTE - alias date_time quoted - alias ndatetime nquoted - - # RFC-3501 & RFC-9051: - # body = "(" (body-type-1part / body-type-mpart) ")" - def body - @lex_state = EXPR_DATA - lpar; result = peek_lpar? ? body_type_mpart : body_type_1part; rpar - result - ensure - @lex_state = EXPR_BEG - end - alias lookahead_body? lookahead_lpar? - - # RFC-3501 & RFC9051: - # body-type-1part = (body-type-basic / body-type-msg / body-type-text) - # [SP body-ext-1part] - def body_type_1part - # This regexp peek is a performance optimization. - # The lookahead fallback would work fine too. - m = peek_re(/\G(?: - (? "TEXT" \s "[^"]+" ) - |(? "MESSAGE" \s "(?:RFC822|GLOBAL)" ) - |(? "[^"]+" \s "[^"]+" ) - |(? "MIXED" ) - )/nix) - choice = m&.named_captures&.compact&.keys&.first - # In practice, the following line should never be used. But the ABNF - # *does* allow literals, and this will handle them. - choice ||= lookahead_case_insensitive__string! - case choice - when "BASIC" then body_type_basic # => BodyTypeBasic - when "MESSAGE" then body_type_msg # => BodyTypeMessage | BodyTypeBasic - when "TEXT" then body_type_text # => BodyTypeText - when "MIXED" then body_type_mixed # => BodyTypeMultipart (server bug) - else body_type_basic # might be a bug; server's or ours? - end - end - - # RFC-3501 & RFC9051: - # body-type-basic = media-basic SP body-fields - def body_type_basic - type = media_basic # n.b. "basic" type isn't enforced here - if lookahead_rpar? then return BodyTypeBasic.new(*type) end # invalid - SP!; flds = body_fields - SP? and exts = body_ext_1part - BodyTypeBasic.new(*type, *flds, *exts) - end - - # RFC-3501 & RFC-9051: - # body-type-text = media-text SP body-fields SP body-fld-lines - def body_type_text - type = media_text - SP!; flds = body_fields - SP!; lines = body_fld_lines - SP? and exts = body_ext_1part - BodyTypeText.new(*type, *flds, lines, *exts) - end - - # RFC-3501 & RFC-9051: - # body-type-msg = media-message SP body-fields SP envelope - # SP body SP body-fld-lines - def body_type_msg - # n.b. "message/rfc822" type isn't enforced here - type = media_message - SP!; flds = body_fields - - # Sometimes servers send body-type-basic when body-type-msg should be. - # E.g: when a message/rfc822 part has "Content-Disposition: attachment". - # - # * SP "(" --> SP envelope --> continue as body-type-msg - # * ")" --> no body-ext-1part --> completed body-type-basic - # * SP nstring --> SP body-fld-md5 - # --> SP body-ext-1part --> continue as body-type-basic - # - # It's probably better to return BodyTypeBasic---even for - # "message/rfc822"---than BodyTypeMessage with invalid fields. - unless peek_str?(" (") - SP? and exts = body_ext_1part - return BodyTypeBasic.new(*type, *flds, *exts) - end - - SP!; env = envelope - SP!; bdy = body - SP!; lines = body_fld_lines - SP? and exts = body_ext_1part - BodyTypeMessage.new(*type, *flds, env, bdy, lines, *exts) - end - - # This is a malformed body-type-mpart with no subparts. - def body_type_mixed - # warn "malformed body-type-mpart: multipart/mixed with no parts." - type = media_subtype # => "MIXED" - SP? and exts = body_ext_mpart - BodyTypeMultipart.new("MULTIPART", type, nil, *exts) - end - - # RFC-3501 & RFC-9051: - # body-type-mpart = 1*body SP media-subtype - # [SP body-ext-mpart] - def body_type_mpart - parts = [body]; parts << body until SP?; msubtype = media_subtype - SP? and exts = body_ext_mpart - BodyTypeMultipart.new("MULTIPART", msubtype, parts, *exts) - end - - # n.b. this handles both type and subtype - # - # RFC-3501 vs RFC-9051: - # media-basic = ((DQUOTE ("APPLICATION" / "AUDIO" / "IMAGE" / - # "MESSAGE" / - # "VIDEO") DQUOTE) / string) SP media-subtype - # media-basic = ((DQUOTE ("APPLICATION" / "AUDIO" / "IMAGE" / - # "FONT" / "MESSAGE" / "MODEL" / - # "VIDEO") DQUOTE) / string) SP media-subtype - # - # media-message = DQUOTE "MESSAGE" DQUOTE SP - # DQUOTE "RFC822" DQUOTE - # media-message = DQUOTE "MESSAGE" DQUOTE SP - # DQUOTE ("RFC822" / "GLOBAL") DQUOTE - # - # RFC-3501 & RFC-9051: - # media-text = DQUOTE "TEXT" DQUOTE SP media-subtype - # media-subtype = string - def media_type - mtype = case_insensitive__string - SP? or return mtype, nil # ??? quirky! - msubtype = media_subtype - return mtype, msubtype - end - - # TODO: check types - alias media_basic media_type # */* --- catchall - alias media_message media_type # message/rfc822, message/global - alias media_text media_type # text/* - - alias media_subtype case_insensitive__string - - # RFC-3501 & RFC-9051: - # body-fields = body-fld-param SP body-fld-id SP body-fld-desc SP - # body-fld-enc SP body-fld-octets - def body_fields - fields = [] - fields << body_fld_param; SP! - fields << body_fld_id; SP! - fields << body_fld_desc; SP! - fields << body_fld_enc; SP! - fields << body_fld_octets - fields - end - - # RFC3501, RFC9051: - # body-fld-param = "(" string SP string *(SP string SP string) ")" / nil - def body_fld_param - return if NIL? - param = {} - lpar - name = case_insensitive__string; SP!; param[name] = string - while SP? - name = case_insensitive__string; SP!; param[name] = string - end - rpar - param - end - - # RFC2060 - # body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp - # [SPACE body_fld_lang - # [SPACE 1#body_extension]]] - # ;; MUST NOT be returned on non-extensible - # ;; "BODY" fetch - # RFC3501 & RFC9051 - # body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang - # [SP body-fld-loc *(SP body-extension)]]] - # ; MUST NOT be returned on non-extensible - # ; "BODY" fetch - def body_ext_1part - fields = []; fields << body_fld_md5 - SP? or return fields; fields << body_fld_dsp - SP? or return fields; fields << body_fld_lang - SP? or return fields; fields << body_fld_loc - SP? or return fields; fields << body_extensions - fields - end - - # RFC-2060: - # body_ext_mpart = body_fld_param [SP body_fld_dsp SP body_fld_lang - # [SP 1#body_extension]] - # ;; MUST NOT be returned on non-extensible - # ;; "BODY" fetch - # RFC-3501 & RFC-9051: - # body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang - # [SP body-fld-loc *(SP body-extension)]]] - # ; MUST NOT be returned on non-extensible - # ; "BODY" fetch - def body_ext_mpart - fields = []; fields << body_fld_param - SP? or return fields; fields << body_fld_dsp - SP? or return fields; fields << body_fld_lang - SP? or return fields; fields << body_fld_loc - SP? or return fields; fields << body_extensions - fields - end - - alias body_fld_desc nstring - alias body_fld_id nstring - alias body_fld_loc nstring - alias body_fld_lines number64 # number in 3501, number64 in 9051 - alias body_fld_md5 nstring - alias body_fld_octets number - - # RFC-3501 & RFC-9051: - # body-fld-enc = (DQUOTE ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ - # "QUOTED-PRINTABLE") DQUOTE) / string - alias body_fld_enc case_insensitive__string - - # body-fld-dsp = "(" string SP body-fld-param ")" / nil - def body_fld_dsp - return if NIL? - lpar; dsp_type = case_insensitive__string - SP!; param = body_fld_param - rpar - ContentDisposition.new(dsp_type, param) - end - - # body-fld-lang = nstring / "(" string *(SP string) ")" - def body_fld_lang - if lpar? - result = [case_insensitive__string] - result << case_insensitive__string while SP? - rpar - result - else - case_insensitive__nstring - end - end - - # body-extension *(SP body-extension) - def body_extensions - result = [] - result << body_extension; while SP? do result << body_extension end - result - end - - # body-extension = nstring / number / number64 / - # "(" body-extension *(SP body-extension) ")" - # ; Future expansion. Client implementations - # ; MUST accept body-extension fields. Server - # ; implementations MUST NOT generate - # ; body-extension fields except as defined by - # ; future Standard or Standards Track - # ; revisions of this specification. - def body_extension - if (uint = number64?) then uint - elsif lpar? then exts = body_extensions; rpar; exts - else nstring - end - end - - # section = "[" [section-spec] "]" - def section - str = +lbra - str << section_spec unless peek_rbra? - str << rbra - end - - # section-binary = "[" [section-part] "]" - def section_binary - str = +lbra - str << section_part unless peek_rbra? - str << rbra - end - - # section-spec = section-msgtext / (section-part ["." section-text]) - # section-msgtext = "HEADER" / - # "HEADER.FIELDS" [".NOT"] SP header-list / - # "TEXT" - # ; top-level or MESSAGE/RFC822 or - # ; MESSAGE/GLOBAL part - # section-part = nz-number *("." nz-number) - # ; body part reference. - # ; Allows for accessing nested body parts. - # section-text = section-msgtext / "MIME" - # ; text other than actual body part (headers, - # ; etc.) - # - # n.b: we could "cheat" here and just grab all text inside the brackets, - # but literals would need special treatment. - def section_spec - str = "".b - str << atom # grabs everything up to "SP header-list" or "]" - str << " " << header_list if SP? - str - end - - # header-list = "(" header-fld-name *(SP header-fld-name) ")" - def header_list - str = +"" - str << lpar << header_fld_name - str << " " << header_fld_name while SP? - str << rpar - end - - # section-part = nz-number *("." nz-number) - # ; body part reference. - # ; Allows for accessing nested body parts. - alias section_part atom - - # RFC3501 & RFC9051: - # header-fld-name = astring - # - # NOTE: Previously, Net::IMAP recreated the raw original source string. - # Now, it grabs the raw encoded value using @str and @pos. A future - # version may simply return the decoded astring value. Although that is - # technically incompatible, it should almost never make a difference: all - # standard header field names are valid atoms: - # - # https://www.iana.org/assignments/message-headers/message-headers.xhtml - # - # Although RFC3501 allows any astring, RFC5322-valid header names are one - # or more of the printable US-ASCII characters, except SP and colon. So - # empty string isn't valid, and literals aren't needed and should not be - # used. This is explicitly unchanged by [I18N-HDRS] (RFC6532). - # - # RFC5233: - # optional-field = field-name ":" unstructured CRLF - # field-name = 1*ftext - # ftext = %d33-57 / ; Printable US-ASCII - # %d59-126 ; characters not including - # ; ":". - def header_fld_name - assert_no_lookahead - start = @pos - astring - @str[start...@pos - 1] - end - - # mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list / - # "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) / - # "STATUS" SP mailbox SP "(" [status-att-list] ")" / - # number SP "EXISTS" / number SP "RECENT" - - def mailbox_data__flags - name = label("FLAGS") - SP! - UntaggedResponse.new(name, flag_list, @str) - end - - def mailbox_data__list - name = label_in("LIST", "LSUB", "XLIST") - SP! - UntaggedResponse.new(name, mailbox_list, @str) - end - alias mailbox_data__lsub mailbox_data__list - alias mailbox_data__xlist mailbox_data__list - - # mailbox-list = "(" [mbx-list-flags] ")" SP - # (DQUOTE QUOTED-CHAR DQUOTE / nil) SP mailbox - # [SP mbox-list-extended] - # ; This is the list information pointed to by the ABNF - # ; item "mailbox-data", which is defined above - def mailbox_list - lpar; attr = peek_rpar? ? [] : mbx_list_flags; rpar - SP!; delim = nquoted - SP!; name = mailbox - # TODO: mbox-list-extended - MailboxList.new(attr, delim, name) - end - - def quota_response - # If quota never established, get back - # `NO Quota root does not exist'. - # If quota removed, get `()' after the - # folder spec with no mention of `STORAGE'. - token = match(T_ATOM) - name = token.value.upcase - match(T_SPACE) - mailbox = astring - match(T_SPACE) - match(T_LPAR) - token = lookahead - case token.symbol - when T_RPAR - shift_token - data = MailboxQuota.new(mailbox, nil, nil) - return UntaggedResponse.new(name, data, @str) - when T_ATOM - shift_token - match(T_SPACE) - token = match(T_NUMBER) - usage = token.value - match(T_SPACE) - token = match(T_NUMBER) - quota = token.value - match(T_RPAR) - data = MailboxQuota.new(mailbox, usage, quota) - return UntaggedResponse.new(name, data, @str) - else - parse_error("unexpected token %s", token.symbol) - end - end - - def quotaroot_response - # Similar to getquota, but only admin can use getquota. - token = match(T_ATOM) - name = token.value.upcase - match(T_SPACE) - mailbox = astring - quotaroots = [] - while true - token = lookahead - break unless token.symbol == T_SPACE - shift_token - quotaroots.push(astring) - end - data = MailboxQuotaRoot.new(mailbox, quotaroots) - return UntaggedResponse.new(name, data, @str) - end - - # acl-data = "ACL" SP mailbox *(SP identifier SP rights) - def acl_data - token = match(T_ATOM) - name = token.value.upcase - match(T_SPACE) - mailbox = astring - data = [] - token = lookahead - if token.symbol == T_SPACE - shift_token - while true - token = lookahead - case token.symbol - when T_CRLF - break - when T_SPACE - shift_token - end - user = astring - match(T_SPACE) - rights = astring - data.push(MailboxACLItem.new(user, rights, mailbox)) - end - end - return UntaggedResponse.new(name, data, @str) - end - - # RFC3501: - # mailbox-data = "SEARCH" *(SP nz-number) / ... - # RFC5256: SORT - # sort-data = "SORT" *(SP nz-number) - # RFC7162: CONDSTORE, QRESYNC - # mailbox-data =/ "SEARCH" [1*(SP nz-number) SP - # search-sort-mod-seq] - # sort-data = "SORT" [1*(SP nz-number) SP - # search-sort-mod-seq] - # ; Updates the SORT response from RFC 5256. - # search-sort-mod-seq = "(" "MODSEQ" SP mod-sequence-value ")" - # RFC9051: - # mailbox-data = obsolete-search-response / ... - # obsolete-search-response = "SEARCH" *(SP nz-number) - def mailbox_data__search - name = label_in("SEARCH", "SORT") - data = [] - while _ = SP? && nz_number? do data << _ end - if lpar? - label("MODSEQ"); SP! - modseq = mod_sequence_value - rpar - end - data = SearchResult.new(data, modseq: modseq) - UntaggedResponse.new(name, data, @str) - end - alias sort_data mailbox_data__search - - # RFC5256: THREAD - # thread-data = "THREAD" [SP 1*thread-list] - def thread_data - name = label("THREAD") - threads = [] - if SP? - threads << thread_list while lookahead_thread_list? - end - UntaggedResponse.new(name, threads, @str) - end - - alias lookahead_thread_list? lookahead_lpar? - alias lookahead_thread_nested? lookahead_thread_list? - - # RFC5256: THREAD - # thread-list = "(" (thread-members / thread-nested) ")" - def thread_list - lpar - thread = if lookahead_thread_nested? - ThreadMember.new(nil, thread_nested) - else - thread_members - end - rpar - thread - end - - # RFC5256: THREAD - # thread-members = nz-number *(SP nz-number) [SP thread-nested] - def thread_members - members = [] - members << nz_number # thread root - while SP? - case lookahead!(T_NUMBER, T_LPAR).symbol - when T_NUMBER then members << nz_number - else nested = thread_nested; break - end - end - members.reverse.inject(nested || []) {|subthreads, number| - [ThreadMember.new(number, subthreads)] - }.first - end - - # RFC5256: THREAD - # thread-nested = 2*thread-list - def thread_nested - nested = [thread_list, thread_list] - while lookahead_thread_list? do nested << thread_list end - nested - end - - # mailbox-data =/ "STATUS" SP mailbox SP "(" [status-att-list] ")" - def mailbox_data__status - resp_name = label("STATUS"); SP! - mbox_name = mailbox; SP! - lpar; attr = status_att_list; rpar - UntaggedResponse.new(resp_name, StatusData.new(mbox_name, attr), @str) - end - - # RFC3501 - # status-att-list = status-att SP number *(SP status-att SP number) - # RFC4466, RFC9051, and RFC3501 Errata - # status-att-list = status-att-val *(SP status-att-val) - def status_att_list - attrs = [status_att_val] - while SP? do attrs << status_att_val end - attrs.to_h - end - - # RFC3501 Errata: - # status-att-val = ("MESSAGES" SP number) / ("RECENT" SP number) / - # ("UIDNEXT" SP nz-number) / ("UIDVALIDITY" SP nz-number) / - # ("UNSEEN" SP number) - # RFC4466: - # status-att-val = ("MESSAGES" SP number) / - # ("RECENT" SP number) / - # ("UIDNEXT" SP nz-number) / - # ("UIDVALIDITY" SP nz-number) / - # ("UNSEEN" SP number) - # ;; Extensions to the STATUS responses - # ;; should extend this production. - # ;; Extensions should use the generic - # ;; syntax defined by tagged-ext. - # RFC9051: - # status-att-val = ("MESSAGES" SP number) / - # ("UIDNEXT" SP nz-number) / - # ("UIDVALIDITY" SP nz-number) / - # ("UNSEEN" SP number) / - # ("DELETED" SP number) / - # ("SIZE" SP number64) - # ; Extensions to the STATUS responses - # ; should extend this production. - # ; Extensions should use the generic - # ; syntax defined by tagged-ext. - # RFC7162: - # status-att-val =/ "HIGHESTMODSEQ" SP mod-sequence-valzer - # ;; Extends non-terminal defined in [RFC4466]. - # ;; Value 0 denotes that the mailbox doesn't - # ;; support persistent mod-sequences - # ;; as described in Section 3.1.2.2. - # RFC7889: - # status-att-val =/ "APPENDLIMIT" SP (number / nil) - # ;; status-att-val is defined in RFC 4466 - # RFC8438: - # status-att-val =/ "SIZE" SP number64 - # RFC8474: - # status-att-val =/ "MAILBOXID" SP "(" objectid ")" - # ; follows tagged-ext production from [RFC4466] - def status_att_val - key = tagged_ext_label - SP! - val = - case key - when "MESSAGES" then number # RFC3501, RFC9051 - when "UNSEEN" then number # RFC3501, RFC9051 - when "DELETED" then number # RFC3501, RFC9051 - when "UIDNEXT" then nz_number # RFC3501, RFC9051 - when "UIDVALIDITY" then nz_number # RFC3501, RFC9051 - when "RECENT" then number # RFC3501 (obsolete) - when "SIZE" then number64 # RFC8483, RFC9051 - when "HIGHESTMODSEQ" then mod_sequence_valzer # RFC7162 - when "MAILBOXID" then parens__objectid # RFC8474 - else - number? || ExtensionData.new(tagged_ext_val) - end - [key, val] - end - - # The presence of "IMAP4rev1" or "IMAP4rev2" is unenforced here. - # The grammar rule is used by both response-data and resp-text-code. - # But this method only returns UntaggedResponse (response-data). - # - # RFC3501: - # capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1" - # *(SP capability) - # RFC9051: - # capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev2" - # *(SP capability) - def capability_data__untagged - UntaggedResponse.new label("CAPABILITY"), capability__list, @str - end - - # enable-data = "ENABLED" *(SP capability) - def enable_data - UntaggedResponse.new label("ENABLED"), capability__list, @str - end - - # As a workaround for buggy servers, allow a trailing SP: - # *(SP capability) [SP] - def capability__list - list = []; while SP? && (capa = capability?) do list << capa end; list - end - - alias resp_code__capability capability__list - - # capability = ("AUTH=" auth-type) / atom - # ; New capabilities MUST begin with "X" or be - # ; registered with IANA as standard or - # ; standards-track - alias capability case_insensitive__atom - alias capability? case_insensitive__atom? - - def id_response - token = match(T_ATOM) - name = token.value.upcase - match(T_SPACE) - token = match(T_LPAR, T_NIL) - if token.symbol == T_NIL - return UntaggedResponse.new(name, nil, @str) - else - data = {} - while true - token = lookahead - case token.symbol - when T_RPAR - shift_token - break - when T_SPACE - shift_token - next - else - key = string - match(T_SPACE) - val = nstring - data[key] = val - end - end - return UntaggedResponse.new(name, data, @str) - end - end - - # namespace-response = "NAMESPACE" SP namespace - # SP namespace SP namespace - # ; The first Namespace is the Personal Namespace(s). - # ; The second Namespace is the Other Users' - # ; Namespace(s). - # ; The third Namespace is the Shared Namespace(s). - def namespace_response - name = label("NAMESPACE") - @lex_state = EXPR_DATA - data = Namespaces.new((SP!; namespace), - (SP!; namespace), - (SP!; namespace)) - UntaggedResponse.new(name, data, @str) - ensure - @lex_state = EXPR_BEG - end - - # namespace = nil / "(" 1*namespace-descr ")" - def namespace - NIL? and return [] - lpar - list = [namespace_descr] - list << namespace_descr until rpar? - list - end - - # namespace-descr = "(" string SP - # (DQUOTE QUOTED-CHAR DQUOTE / nil) - # [namespace-response-extensions] ")" - def namespace_descr - lpar - prefix = string; SP! - delimiter = nquoted # n.b: should only accept single char - extensions = namespace_response_extensions - rpar - Namespace.new(prefix, delimiter, extensions) - end - - # namespace-response-extensions = *namespace-response-extension - # namespace-response-extension = SP string SP - # "(" string *(SP string) ")" - def namespace_response_extensions - data = {} - while SP? - name = string; SP! - lpar - data[name] ||= [] - data[name] << string - data[name] << string while SP? - rpar - end - data - end - - # TEXT-CHAR = - # RFC3501: - # text = 1*TEXT-CHAR - # RFC9051: - # text = 1*(TEXT-CHAR / UTF8-2 / UTF8-3 / UTF8-4) - # ; Non-ASCII text can only be returned - # ; after ENABLE IMAP4rev2 command - def text - match_re(TEXT_REGEXP, "text")[0].force_encoding("UTF-8") - end - - # an "accept" versiun of #text - def text? - accept_re(TEXT_REGEXP)&.[](0)&.force_encoding("UTF-8") - end - - # RFC3501: - # resp-text = ["[" resp-text-code "]" SP] text - # RFC9051: - # resp-text = ["[" resp-text-code "]" SP] [text] - # - # We leniently re-interpret this as - # resp-text = ["[" resp-text-code "]" [SP [text]] / [text] - def resp_text - if lbra? - code = resp_text_code; rbra - ResponseText.new(code, SP? && text? || "") - else - ResponseText.new(nil, text? || "") - end - end - - # RFC3501 (See https://www.rfc-editor.org/errata/rfc3501): - # resp-text-code = "ALERT" / - # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] / - # capability-data / "PARSE" / - # "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" / - # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / - # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number / - # "UNSEEN" SP nz-number / - # atom [SP 1*] - # capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1" - # *(SP capability) - # - # RFC5530: - # resp-text-code =/ "UNAVAILABLE" / "AUTHENTICATIONFAILED" / - # "AUTHORIZATIONFAILED" / "EXPIRED" / - # "PRIVACYREQUIRED" / "CONTACTADMIN" / "NOPERM" / - # "INUSE" / "EXPUNGEISSUED" / "CORRUPTION" / - # "SERVERBUG" / "CLIENTBUG" / "CANNOT" / - # "LIMIT" / "OVERQUOTA" / "ALREADYEXISTS" / - # "NONEXISTENT" - # RFC9051: - # resp-text-code = "ALERT" / - # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] / - # capability-data / "PARSE" / - # "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" / - # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / - # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number / - # resp-code-apnd / resp-code-copy / "UIDNOTSTICKY" / - # "UNAVAILABLE" / "AUTHENTICATIONFAILED" / - # "AUTHORIZATIONFAILED" / "EXPIRED" / - # "PRIVACYREQUIRED" / "CONTACTADMIN" / "NOPERM" / - # "INUSE" / "EXPUNGEISSUED" / "CORRUPTION" / - # "SERVERBUG" / "CLIENTBUG" / "CANNOT" / - # "LIMIT" / "OVERQUOTA" / "ALREADYEXISTS" / - # "NONEXISTENT" / "NOTSAVED" / "HASCHILDREN" / - # "CLOSED" / - # "UNKNOWN-CTE" / - # atom [SP 1*] - # capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev2" - # *(SP capability) - # - # RFC4315 (UIDPLUS), RFC9051 (IMAP4rev2): - # resp-code-apnd = "APPENDUID" SP nz-number SP append-uid - # resp-code-copy = "COPYUID" SP nz-number SP uid-set SP uid-set - # resp-text-code =/ resp-code-apnd / resp-code-copy / "UIDNOTSTICKY" - # - # RFC7162 (CONDSTORE): - # resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value / - # "NOMODSEQ" / - # "MODIFIED" SP sequence-set - # RFC7162 (QRESYNC): - # resp-text-code =/ "CLOSED" - # - # RFC8474: OBJECTID - # resp-text-code =/ "MAILBOXID" SP "(" objectid ")" - def resp_text_code - name = resp_text_code__name - data = - case name - when "CAPABILITY" then resp_code__capability - when "PERMANENTFLAGS" then SP? ? flag_perm__list : [] - when "UIDNEXT" then SP!; nz_number - when "UIDVALIDITY" then SP!; nz_number - when "UNSEEN" then SP!; nz_number # rev1 only - when "APPENDUID" then SP!; resp_code_apnd__data # rev2, UIDPLUS - when "COPYUID" then SP!; resp_code_copy__data # rev2, UIDPLUS - when "BADCHARSET" then SP? ? charset__list : [] - when "ALERT", "PARSE", "READ-ONLY", "READ-WRITE", "TRYCREATE", - "UNAVAILABLE", "AUTHENTICATIONFAILED", "AUTHORIZATIONFAILED", - "EXPIRED", "PRIVACYREQUIRED", "CONTACTADMIN", "NOPERM", "INUSE", - "EXPUNGEISSUED", "CORRUPTION", "SERVERBUG", "CLIENTBUG", "CANNOT", - "LIMIT", "OVERQUOTA", "ALREADYEXISTS", "NONEXISTENT", "CLOSED", - "NOTSAVED", "UIDNOTSTICKY", "UNKNOWN-CTE", "HASCHILDREN" - when "NOMODSEQ" then nil # CONDSTORE - when "HIGHESTMODSEQ" then SP!; mod_sequence_value # CONDSTORE - when "MODIFIED" then SP!; sequence_set # CONDSTORE - when "MAILBOXID" then SP!; parens__objectid # RFC8474: OBJECTID - else - SP? and text_chars_except_rbra - end - ResponseCode.new(name, data) - end - - alias resp_text_code__name case_insensitive__atom - - # 1* - def text_chars_except_rbra - match_re(CTEXT_REGEXP, '1*')[0] - end - - # "(" charset *(SP charset) ")" - def charset__list - lpar; list = [charset]; while SP? do list << charset end; rpar; list - end - - # already matched: "APPENDUID" - # - # +UIDPLUS+ ABNF:: https://www.rfc-editor.org/rfc/rfc4315.html#section-4 - # resp-code-apnd = "APPENDUID" SP nz-number SP append-uid - # append-uid = uniqueid - # append-uid =/ uid-set - # ; only permitted if client uses [MULTIAPPEND] - # ; to append multiple messages. - # - # n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always - # match uid_set even if that returns a single-member array. - # - def resp_code_apnd__data - validity = number; SP! - dst_uids = uid_set # uniqueid ⊂ uid-set - UIDPlusData.new(validity, nil, dst_uids) - end - - # already matched: "COPYUID" - # - # resp-code-copy = "COPYUID" SP nz-number SP uid-set SP uid-set - def resp_code_copy__data - validity = number; SP! - src_uids = uid_set; SP! - dst_uids = uid_set - UIDPlusData.new(validity, src_uids, dst_uids) - end - - ADDRESS_REGEXP = /\G - \( (?: NIL | #{Patterns::QUOTED_rev2} ) # 1: NAME - \s (?: NIL | #{Patterns::QUOTED_rev2} ) # 2: ROUTE - \s (?: NIL | #{Patterns::QUOTED_rev2} ) # 3: MAILBOX - \s (?: NIL | #{Patterns::QUOTED_rev2} ) # 4: HOST - \) - /nix - - # address = "(" addr-name SP addr-adl SP addr-mailbox SP - # addr-host ")" - # addr-adl = nstring - # addr-host = nstring - # addr-mailbox = nstring - # addr-name = nstring - def address - if (match = accept_re(ADDRESS_REGEXP)) - # note that "NIL" isn't captured by the regexp - name, route, mailbox, host = match.captures - .map { Patterns.unescape_quoted _1 } - else # address may include literals - lpar; name = addr_name - SP!; route = addr_adl - SP!; mailbox = addr_mailbox - SP!; host = addr_host - rpar - end - Address.new(name, route, mailbox, host) - end - - alias addr_adl nstring - alias addr_host nstring - alias addr_mailbox nstring - alias addr_name nstring - - # flag-list = "(" [flag *(SP flag)] ")" - def flag_list - if (match = accept_re(Patterns::FLAG_LIST)) - match[1].split(nil) - .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 } - else - quirky__flag_list "flags-list" - end - end - - # "(" [flag-perm *(SP flag-perm)] ")" - def flag_perm__list - if (match = accept_re(Patterns::FLAG_PERM_LIST)) - match[1].split(nil) - .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 } - else - quirky__flag_list "PERMANENTFLAGS flag-perm list" - end - end - - # This allows illegal "]" in flag names (Gmail), - # or "\*" in a FLAGS response (greenmail). - def quirky__flag_list(name) - match_re(Patterns::QUIRKY_FLAGS_LIST, "quirks mode #{name}")[1] - .scan(Patterns::QUIRKY_FLAG) - .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 } - end - - # See Patterns::MBX_LIST_FLAGS - def mbx_list_flags - match_re(Patterns::MBX_LIST_FLAGS, "mbx-list-flags")[1] - .split(nil) - .map! { _1.delete_prefix!("\\"); _1.capitalize.to_sym } - end - - # See https://developers.google.com/gmail/imap/imap-extensions - def x_gm_label; accept(T_BSLASH) ? atom.capitalize.to_sym : astring end - - # See https://developers.google.com/gmail/imap/imap-extensions - def x_gm_labels - lpar; return [] if rpar? - labels = [] - labels << x_gm_label - labels << x_gm_label while SP? - rpar - labels - end - - # See https://www.rfc-editor.org/errata/rfc3501 - # - # charset = atom / quoted - def charset; quoted? || atom end - - # RFC7162: - # mod-sequence-value = 1*DIGIT - # ;; Positive unsigned 63-bit integer - # ;; (mod-sequence) - # ;; (1 <= n <= 9,223,372,036,854,775,807). - alias mod_sequence_value nz_number64 - - # RFC7162: - # permsg-modsequence = mod-sequence-value - # ;; Per-message mod-sequence. - alias permsg_modsequence mod_sequence_value - - # RFC7162: - # mod-sequence-valzer = "0" / mod-sequence-value - alias mod_sequence_valzer number64 - - def parens__modseq; lpar; _ = permsg_modsequence; rpar; _ end - - # RFC8474: - # objectid = 1*255(ALPHA / DIGIT / "_" / "-") - # ; characters in object identifiers are case - # ; significant - alias objectid atom - - def parens__objectid; lpar; _ = objectid; rpar; _ end - def nparens__objectid; NIL? ? nil : parens__objectid end - - # RFC-4315 (UIDPLUS) or RFC9051 (IMAP4rev2): - # uid-set = (uniqueid / uid-range) *("," uid-set) - # uid-range = (uniqueid ":" uniqueid) - # ; two uniqueid values and all values - # ; between these two regardless of order. - # ; Example: 2:4 and 4:2 are equivalent. - # uniqueid = nz-number - # ; Strictly ascending - def uid_set - token = match(T_NUMBER, T_ATOM) - case token.symbol - when T_NUMBER then [Integer(token.value)] - when T_ATOM - token.value.split(",").flat_map {|range| - range = range.split(":").map {|uniqueid| Integer(uniqueid) } - range.size == 1 ? range : Range.new(range.min, range.max).to_a - } - end - end - - def nil_atom - match(T_NIL) - return nil - end - - SPACES_REGEXP = /\G */n - - # The RFC is very strict about this and usually we should be too. - # But skipping spaces is usually a safe workaround for buggy servers. - # - # This advances @pos directly so it's safe before changing @lex_state. - def accept_spaces - return false unless SP? - @str.index(SPACES_REGEXP, @pos) and - @pos = $~.end(0) - true - end - - def next_token - case @lex_state - when EXPR_BEG - if @str.index(BEG_REGEXP, @pos) - @pos = $~.end(0) - if $1 - return Token.new(T_SPACE, $+) - elsif $2 - len = $+.to_i - val = @str[@pos, len] - @pos += len - return Token.new(T_LITERAL8, val) - elsif $3 && $7 - # greedily match ATOM, prefixed with NUMBER, NIL, or PLUS. - return Token.new(T_ATOM, $3) - elsif $4 - return Token.new(T_NIL, $+) - elsif $5 - return Token.new(T_NUMBER, $+) - elsif $6 - return Token.new(T_PLUS, $+) - elsif $8 - # match ATOM, without a NUMBER, NIL, or PLUS prefix - return Token.new(T_ATOM, $+) - elsif $9 - return Token.new(T_QUOTED, Patterns.unescape_quoted($+)) - elsif $10 - return Token.new(T_LPAR, $+) - elsif $11 - return Token.new(T_RPAR, $+) - elsif $12 - return Token.new(T_BSLASH, $+) - elsif $13 - return Token.new(T_STAR, $+) - elsif $14 - return Token.new(T_LBRA, $+) - elsif $15 - return Token.new(T_RBRA, $+) - elsif $16 - len = $+.to_i - val = @str[@pos, len] - @pos += len - return Token.new(T_LITERAL, val) - elsif $17 - return Token.new(T_PERCENT, $+) - elsif $18 - return Token.new(T_CRLF, $+) - elsif $19 - return Token.new(T_EOF, $+) - else - parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid") - end - else - @str.index(/\S*/n, @pos) - parse_error("unknown token - %s", $&.dump) - end - when EXPR_DATA - if @str.index(DATA_REGEXP, @pos) - @pos = $~.end(0) - if $1 - return Token.new(T_SPACE, $+) - elsif $2 - return Token.new(T_NIL, $+) - elsif $3 - return Token.new(T_NUMBER, $+) - elsif $4 - return Token.new(T_QUOTED, Patterns.unescape_quoted($+)) - elsif $5 - len = $+.to_i - val = @str[@pos, len] - @pos += len - return Token.new(T_LITERAL, val) - elsif $6 - return Token.new(T_LPAR, $+) - elsif $7 - return Token.new(T_RPAR, $+) - else - parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid") - end - else - @str.index(/\S*/n, @pos) - parse_error("unknown token - %s", $&.dump) - end - else - parse_error("invalid @lex_state - %s", @lex_state.inspect) - end - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/anonymous_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/anonymous_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/anonymous_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/anonymous_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - module SASL - - # Authenticator for the "+ANONYMOUS+" SASL mechanism, as specified by - # RFC-4505[https://tools.ietf.org/html/rfc4505]. See - # Net::IMAP#authenticate. - class AnonymousAuthenticator - - # An optional token sent for the +ANONYMOUS+ mechanism., up to 255 UTF-8 - # characters in length. - # - # If it contains an "@" sign, the message must be a valid email address - # (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]). - # Email syntax is _not_ validated by AnonymousAuthenticator. - # - # Otherwise, it can be any UTF8 string which is permitted by the - # StringPrep::Trace profile. - attr_reader :anonymous_message - - # :call-seq: - # new(anonymous_message = "", **) -> authenticator - # new(anonymous_message: "", **) -> authenticator - # - # Creates an Authenticator for the "+ANONYMOUS+" SASL mechanism, as - # specified in RFC-4505[https://tools.ietf.org/html/rfc4505]. To use - # this, see Net::IMAP#authenticate or your client's authentication - # method. - # - # ==== Parameters - # - # * _optional_ #anonymous_message — a message to send to the server. - # - # Any other keyword arguments are silently ignored. - def initialize(anon_msg = nil, anonymous_message: nil, **) - message = (anonymous_message || anon_msg || "").to_str - @anonymous_message = StringPrep::Trace.stringprep_trace message - if (size = @anonymous_message&.length)&.> 255 - raise ArgumentError, - "anonymous_message is too long. (%d codepoints)" % [size] - end - @done = false - end - - # :call-seq: - # initial_response? -> true - # - # +ANONYMOUS+ can send an initial client response. - def initial_response?; true end - - # Returns #anonymous_message. - def process(_server_challenge_string) - anonymous_message - ensure - @done = true - end - - # Returns true when the initial client response was sent. - # - # The authentication should not succeed unless this returns true, but it - # does *not* indicate success. - def done?; @done end - - end - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/authentication_exchange.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/authentication_exchange.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/authentication_exchange.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/authentication_exchange.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,107 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - module SASL - - # This API is *experimental*, and may change. - # - # TODO: catch exceptions in #process and send #cancel_response. - # TODO: raise an error if the command succeeds after being canceled. - # TODO: use with more clients, to verify the API can accommodate them. - # - # Create an AuthenticationExchange from a client adapter and a mechanism - # authenticator: - # def authenticate(mechanism, ...) - # authenticator = SASL.authenticator(mechanism, ...) - # SASL::AuthenticationExchange.new( - # sasl_adapter, mechanism, authenticator - # ).authenticate - # end - # - # private - # - # def sasl_adapter = MyClientAdapter.new(self, &method(:send_command)) - # - # Or delegate creation of the authenticator to ::build: - # def authenticate(...) - # SASL::AuthenticationExchange.build(sasl_adapter, ...) - # .authenticate - # end - # - # As a convenience, ::authenticate combines ::build and #authenticate: - # def authenticate(...) - # SASL::AuthenticationExchange.authenticate(sasl_adapter, ...) - # end - # - # Likewise, ClientAdapter#authenticate delegates to #authenticate: - # def authenticate(...) = sasl_adapter.authenticate(...) - # - class AuthenticationExchange - # Convenience method for build(...).authenticate - def self.authenticate(...) build(...).authenticate end - - # Use +registry+ to override the global Authenticators registry. - def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block) - authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block) - new(client, mechanism, authenticator, sasl_ir: sasl_ir) - end - - attr_reader :mechanism, :authenticator - - def initialize(client, mechanism, authenticator, sasl_ir: true) - @client = client - @mechanism = -mechanism.to_s.upcase.tr(?_, ?-) - @authenticator = authenticator - @sasl_ir = sasl_ir - @processed = false - end - - # Call #authenticate to execute an authentication exchange for #client - # using #authenticator. Authentication failures will raise an - # exception. Any exceptions other than those in RESPONSE_ERRORS will - # drop the connection. - def authenticate - client.run_command(mechanism, initial_response) { process _1 } - .tap { raise AuthenticationIncomplete, _1 unless done? } - rescue *client.response_errors - raise # but don't drop the connection - rescue - client.drop_connection - raise - rescue Exception # rubocop:disable Lint/RescueException - client.drop_connection! - raise - end - - def send_initial_response? - @sasl_ir && - authenticator.respond_to?(:initial_response?) && - authenticator.initial_response? && - client.sasl_ir_capable? && - client.auth_capable?(mechanism) - end - - def done? - authenticator.respond_to?(:done?) ? authenticator.done? : @processed - end - - private - - attr_reader :client - - def initial_response - return unless send_initial_response? - client.encode_ir authenticator.process nil - end - - def process(challenge) - client.encode authenticator.process client.decode challenge - ensure - @processed = true - end - - end - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/authenticators.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/authenticators.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/authenticators.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/authenticators.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,118 +0,0 @@ -# frozen_string_literal: true - -module Net::IMAP::SASL - - # Registry for SASL authenticators - # - # Registered authenticators must respond to +#new+ or +#call+ (e.g. a class or - # a proc), receiving any credentials and options and returning an - # authenticator instance. The returned object represents a single - # authentication exchange and must not be reused for multiple - # authentication attempts. - # - # An authenticator instance object must respond to +#process+, receiving the - # server's challenge and returning the client's response. Optionally, it may - # also respond to +#initial_response?+ and +#done?+. When - # +#initial_response?+ returns +true+, +#process+ may be called the first - # time with +nil+. When +#done?+ returns +false+, the exchange is incomplete - # and an exception should be raised if the exchange terminates prematurely. - # - # See the source for PlainAuthenticator, XOAuth2Authenticator, and - # ScramSHA1Authenticator for examples. - class Authenticators - - # Create a new Authenticators registry. - # - # This class is usually not instantiated directly. Use SASL.authenticators - # to reuse the default global registry. - # - # When +use_defaults+ is +false+, the registry will start empty. When - # +use_deprecated+ is +false+, deprecated authenticators will not be - # included with the defaults. - def initialize(use_defaults: true, use_deprecated: true) - @authenticators = {} - return unless use_defaults - add_authenticator "Anonymous" - add_authenticator "External" - add_authenticator "OAuthBearer" - add_authenticator "Plain" - add_authenticator "Scram-SHA-1" - add_authenticator "Scram-SHA-256" - add_authenticator "XOAuth2" - return unless use_deprecated - add_authenticator "Login" # deprecated - add_authenticator "Cram-MD5" # deprecated - add_authenticator "Digest-MD5" # deprecated - end - - # Returns the names of all registered SASL mechanisms. - def names; @authenticators.keys end - - # :call-seq: - # add_authenticator(mechanism) - # add_authenticator(mechanism, authenticator_class) - # add_authenticator(mechanism, authenticator_proc) - # - # Registers an authenticator for #authenticator to use. +mechanism+ is the - # name of the - # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] - # implemented by +authenticator_class+ (for instance, "PLAIN"). - # - # If +mechanism+ refers to an existing authenticator, - # the old authenticator will be replaced. - # - # When only a single argument is given, the authenticator class will be - # lazily loaded from Net::IMAP::SASL::#{name}Authenticator (case is - # preserved and non-alphanumeric characters are removed.. - def add_authenticator(name, authenticator = nil) - key = -name.to_s.upcase.tr(?_, ?-) - authenticator ||= begin - class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym - auth_class = nil - ->(*creds, **props, &block) { - auth_class ||= Net::IMAP::SASL.const_get(class_name) - auth_class.new(*creds, **props, &block) - } - end - @authenticators[key] = authenticator - end - - # Removes the authenticator registered for +name+ - def remove_authenticator(name) - key = -name.to_s.upcase.tr(?_, ?-) - @authenticators.delete(key) - end - - def mechanism?(name) - key = -name.to_s.upcase.tr(?_, ?-) - @authenticators.key?(key) - end - - # :call-seq: - # authenticator(mechanism, ...) -> auth_session - # - # Builds an authenticator instance using the authenticator registered to - # +mechanism+. The returned object represents a single authentication - # exchange and must not be reused for multiple authentication - # attempts. - # - # All arguments (except +mechanism+) are forwarded to the registered - # authenticator's +#new+ or +#call+ method. Each authenticator must - # document its own arguments. - # - # [Note] - # This method is intended for internal use by connection protocol code - # only. Protocol client users should see refer to their client's - # documentation, e.g. Net::IMAP#authenticate. - def authenticator(mechanism, ...) - key = -mechanism.to_s.upcase.tr(?_, ?-) - auth = @authenticators.fetch(key) do - raise ArgumentError, 'unknown auth type - "%s"' % key - end - auth.respond_to?(:new) ? auth.new(...) : auth.call(...) - end - alias new authenticator - - end - -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/client_adapter.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/client_adapter.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/client_adapter.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/client_adapter.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - module SASL - - # This API is *experimental*, and may change. - # - # TODO: use with more clients, to verify the API can accommodate them. - # - # An abstract base class for implementing a SASL authentication exchange. - # Different clients will each have their own adapter subclass, overridden - # to match their needs. - # - # Although the default implementations _may_ be sufficient, subclasses - # will probably need to override some methods. Additionally, subclasses - # may need to include a protocol adapter mixin, if the default - # ProtocolAdapters::Generic isn't sufficient. - class ClientAdapter - include ProtocolAdapters::Generic - - attr_reader :client, :command_proc - - # +command_proc+ can used to avoid exposing private methods on #client. - # It should run a command with the arguments sent to it, yield each - # continuation payload, respond to the server with the result of each - # yield, and return the result. Non-successful results *MUST* raise an - # exception. Exceptions in the block *MUST* cause the command to fail. - # - # Subclasses that override #run_command may use #command_proc for - # other purposes. - def initialize(client, &command_proc) - @client, @command_proc = client, command_proc - end - - # Delegates to AuthenticationExchange.authenticate. - def authenticate(...) AuthenticationExchange.authenticate(self, ...) end - - # Do the protocol and server both support an initial response? - def sasl_ir_capable?; client.sasl_ir_capable? end - - # Does the server advertise support for the mechanism? - def auth_capable?(mechanism); client.auth_capable?(mechanism) end - - # Runs the authenticate command with +mechanism+ and +initial_response+. - # When +initial_response+ is nil, an initial response must NOT be sent. - # - # Yields each continuation payload, responds to the server with the - # result of each yield, and returns the result. Non-successful results - # *MUST* raise an exception. Exceptions in the block *MUST* cause the - # command to fail. - # - # Subclasses that override this may use #command_proc differently. - def run_command(mechanism, initial_response = nil, &block) - command_proc or raise Error, "initialize with block or override" - args = [command_name, mechanism, initial_response].compact - command_proc.call(*args, &block) - end - - # Returns an array of server responses errors raised by run_command. - # Exceptions in this array won't drop the connection. - def response_errors; [] end - - # Drop the connection gracefully. - def drop_connection; client.drop_connection end - - # Drop the connection abruptly. - def drop_connection!; client.drop_connection! end - end - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/cram_md5_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/cram_md5_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/cram_md5_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/cram_md5_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -# Authenticator for the "+CRAM-MD5+" SASL mechanism, specified in -# RFC2195[https://tools.ietf.org/html/rfc2195]. See Net::IMAP#authenticate. -# -# == Deprecated -# -# +CRAM-MD5+ is obsolete and insecure. It is included for compatibility with -# existing servers. -# {draft-ietf-sasl-crammd5-to-historic}[https://tools.ietf.org/html/draft-ietf-sasl-crammd5-to-historic-00.html] -# recommends using +SCRAM-*+ or +PLAIN+ protected by TLS instead. -# -# Additionally, RFC8314[https://tools.ietf.org/html/rfc8314] discourage the use -# of cleartext and recommends TLS version 1.2 or greater be used for all -# traffic. With TLS +CRAM-MD5+ is okay, but so is +PLAIN+ -class Net::IMAP::SASL::CramMD5Authenticator - def initialize(user = nil, pass = nil, - authcid: nil, username: nil, - password: nil, secret: nil, - warn_deprecation: true, - **) - if warn_deprecation - warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM - end - require "digest/md5" - @user = authcid || username || user - @password = password || secret || pass - @done = false - end - - def initial_response?; false end - - def process(challenge) - digest = hmac_md5(challenge, @password) - return @user + " " + digest - ensure - @done = true - end - - def done?; @done end - - private - - def hmac_md5(text, key) - if key.length > 64 - key = Digest::MD5.digest(key) - end - - k_ipad = key + "\0" * (64 - key.length) - k_opad = key + "\0" * (64 - key.length) - for i in 0..63 - k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr - k_opad[i] = (k_opad[i].ord ^ 0x5c).chr - end - - digest = Digest::MD5.digest(k_ipad + text) - - return Digest::MD5.hexdigest(k_opad + digest) - end - -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/digest_md5_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/digest_md5_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/digest_md5_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/digest_md5_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,180 +0,0 @@ -# frozen_string_literal: true - -# Net::IMAP authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified -# in RFC-2831[https://tools.ietf.org/html/rfc2831]. See Net::IMAP#authenticate. -# -# == Deprecated -# -# "+DIGEST-MD5+" has been deprecated by -# RFC-6331[https://tools.ietf.org/html/rfc6331] and should not be relied on for -# security. It is included for compatibility with existing servers. -class Net::IMAP::SASL::DigestMD5Authenticator - STAGE_ONE = :stage_one - STAGE_TWO = :stage_two - STAGE_DONE = :stage_done - private_constant :STAGE_ONE, :STAGE_TWO, :STAGE_DONE - - # Authentication identity: the identity that matches the #password. - # - # RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+. - # "Authentication identity" is the generic term used by - # RFC-4422[https://tools.ietf.org/html/rfc4422]. - # RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate - # this to +authcid+. - attr_reader :username - alias authcid username - - # A password or passphrase that matches the #username. - # - # The +password+ will be used to create the response digest. - attr_reader :password - - # Authorization identity: an identity to act as or on behalf of. The identity - # form is application protocol specific. If not provided or left blank, the - # server derives an authorization identity from the authentication identity. - # The server is responsible for verifying the client's credentials and - # verifying that the identity it associates with the client's authentication - # identity is allowed to act as (or on behalf of) the authorization identity. - # - # For example, an administrator or superuser might take on another role: - # - # imap.authenticate "DIGEST-MD5", "root", ->{passwd}, authzid: "user" - # - attr_reader :authzid - - # :call-seq: - # new(username, password, authzid = nil, **options) -> authenticator - # new(username:, password:, authzid: nil, **options) -> authenticator - # new(authcid:, password:, authzid: nil, **options) -> authenticator - # - # Creates an Authenticator for the "+DIGEST-MD5+" SASL mechanism. - # - # Called by Net::IMAP#authenticate and similar methods on other clients. - # - # ==== Parameters - # - # * #authcid ― Authentication identity that is associated with #password. - # - # #username ― An alias for +authcid+. - # - # * #password ― A password or passphrase associated with this #authcid. - # - # * _optional_ #authzid ― Authorization identity to act as or on behalf of. - # - # When +authzid+ is not set, the server should derive the authorization - # identity from the authentication identity. - # - # * _optional_ +warn_deprecation+ — Set to +false+ to silence the warning. - # - # Any other keyword arguments are silently ignored. - def initialize(user = nil, pass = nil, authz = nil, - username: nil, password: nil, authzid: nil, - authcid: nil, secret: nil, - warn_deprecation: true, **) - username = authcid || username || user or - raise ArgumentError, "missing username (authcid)" - password ||= secret || pass or raise ArgumentError, "missing password" - authzid ||= authz - if warn_deprecation - warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331." - # TODO: recommend SCRAM instead. - end - require "digest/md5" - require "strscan" - @username, @password, @authzid = username, password, authzid - @nc, @stage = {}, STAGE_ONE - end - - def initial_response?; false end - - # Responds to server challenge in two stages. - def process(challenge) - case @stage - when STAGE_ONE - @stage = STAGE_TWO - sparams = {} - c = StringScanner.new(challenge) - while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]|\\.)*"|[^,]+)\s*/) - k, v = c[1], c[2] - if v =~ /^"(.*)"$/ - v = $1 - if v =~ /,/ - v = v.split(',') - end - end - sparams[k] = v - end - - raise Net::IMAP::DataFormatError, "Bad Challenge: '#{challenge}'" unless c.eos? and sparams['qop'] - raise Net::IMAP::Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth") - - response = { - :nonce => sparams['nonce'], - :username => @username, - :realm => sparams['realm'], - :cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]), - :'digest-uri' => 'imap/' + sparams['realm'], - :qop => 'auth', - :maxbuf => 65535, - :nc => "%08d" % nc(sparams['nonce']), - :charset => sparams['charset'], - } - - response[:authzid] = @authzid unless @authzid.nil? - - # now, the real thing - a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') ) - - a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':') - a1 << ':' + response[:authzid] unless response[:authzid].nil? - - a2 = "AUTHENTICATE:" + response[:'digest-uri'] - a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/ - - response[:response] = Digest::MD5.hexdigest( - [ - Digest::MD5.hexdigest(a1), - response.values_at(:nonce, :nc, :cnonce, :qop), - Digest::MD5.hexdigest(a2) - ].join(':') - ) - - return response.keys.map {|key| qdval(key.to_s, response[key]) }.join(',') - when STAGE_TWO - @stage = STAGE_DONE - # if at the second stage, return an empty string - if challenge =~ /rspauth=/ - return '' - else - raise ResponseParseError, challenge - end - else - raise ResponseParseError, challenge - end - end - - def done?; @stage == STAGE_DONE end - - private - - def nc(nonce) - if @nc.has_key? nonce - @nc[nonce] = @nc[nonce] + 1 - else - @nc[nonce] = 1 - end - return @nc[nonce] - end - - # some responses need quoting - def qdval(k, v) - return if k.nil? or v.nil? - if %w"username authzid realm nonce cnonce digest-uri qop".include? k - v = v.gsub(/([\\"])/, "\\\1") - return '%s="%s"' % [k, v] - else - return '%s=%s' % [k, v] - end - end - -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/external_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/external_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/external_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/external_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - module SASL - - # Authenticator for the "+EXTERNAL+" SASL mechanism, as specified by - # RFC-4422[https://tools.ietf.org/html/rfc4422]. See - # Net::IMAP#authenticate. - # - # The EXTERNAL mechanism requests that the server use client credentials - # established external to SASL, for example by TLS certificate or IPsec. - class ExternalAuthenticator - - # Authorization identity: an identity to act as or on behalf of. The - # identity form is application protocol specific. If not provided or - # left blank, the server derives an authorization identity from the - # authentication identity. The server is responsible for verifying the - # client's credentials and verifying that the identity it associates - # with the client's authentication identity is allowed to act as (or on - # behalf of) the authorization identity. - # - # For example, an administrator or superuser might take on another role: - # - # imap.authenticate "PLAIN", "root", passwd, authzid: "user" - # - attr_reader :authzid - alias username authzid - - # :call-seq: - # new(authzid: nil, **) -> authenticator - # new(username: nil, **) -> authenticator - # new(username = nil, **) -> authenticator - # - # Creates an Authenticator for the "+EXTERNAL+" SASL mechanism, as - # specified in RFC-4422[https://tools.ietf.org/html/rfc4422]. To use - # this, see Net::IMAP#authenticate or your client's authentication - # method. - # - # ==== Parameters - # - # * _optional_ #authzid ― Authorization identity to act as or on behalf of. - # - # _optional_ #username ― An alias for #authzid. - # - # Note that, unlike some other authenticators, +username+ sets the - # _authorization_ identity and not the _authentication_ identity. The - # authentication identity is established for the client by the - # external credentials. - # - # Any other keyword parameters are quietly ignored. - def initialize(user = nil, authzid: nil, username: nil, **) - authzid ||= username || user - @authzid = authzid&.to_str&.encode "UTF-8" - if @authzid&.match?(/\u0000/u) # also validates UTF8 encoding - raise ArgumentError, "contains NULL" - end - @done = false - end - - # :call-seq: - # initial_response? -> true - # - # +EXTERNAL+ can send an initial client response. - def initial_response?; true end - - # Returns #authzid, or an empty string if there is no authzid. - def process(_) - authzid || "" - ensure - @done = true - end - - # Returns true when the initial client response was sent. - # - # The authentication should not succeed unless this returns true, but it - # does *not* indicate success. - def done?; @done end - - end - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/gs2_header.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/gs2_header.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/gs2_header.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/gs2_header.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - module SASL - - # Originally defined for the GS2 mechanism family in - # RFC5801[https://tools.ietf.org/html/rfc5801], - # several different mechanisms start with a GS2 header: - # * +GS2-*+ --- RFC5801[https://tools.ietf.org/html/rfc5801] - # * +SCRAM-*+ --- RFC5802[https://tools.ietf.org/html/rfc5802] - # (ScramAuthenticator) - # * +SAML20+ --- RFC6595[https://tools.ietf.org/html/rfc6595] - # * +OPENID20+ --- RFC6616[https://tools.ietf.org/html/rfc6616] - # * +OAUTH10A+ --- RFC7628[https://tools.ietf.org/html/rfc7628] - # * +OAUTHBEARER+ --- RFC7628[https://tools.ietf.org/html/rfc7628] - # (OAuthBearerAuthenticator) - # - # Classes that include this module must implement +#authzid+. - module GS2Header - NO_NULL_CHARS = /\A[^\x00]+\z/u.freeze # :nodoc: - - ## - # Matches {RFC5801 §4}[https://www.rfc-editor.org/rfc/rfc5801#section-4] - # +saslname+. The output from gs2_saslname_encode matches this Regexp. - RFC5801_SASLNAME = /\A(?:[^,=\x00]|=2C|=3D)+\z/u.freeze - - # The {RFC5801 §4}[https://www.rfc-editor.org/rfc/rfc5801#section-4] - # +gs2-header+, which prefixes the #initial_client_response. - # - # >>> - # Note: the actual GS2 header includes an optional flag to - # indicate that the GSS mechanism is not "standard", but since all of - # the SASL mechanisms using GS2 are "standard", we don't include that - # flag. A class for a nonstandard GSSAPI mechanism should prefix with - # "+F,+". - def gs2_header - "#{gs2_cb_flag},#{gs2_authzid}," - end - - # The {RFC5801 §4}[https://www.rfc-editor.org/rfc/rfc5801#section-4] - # +gs2-cb-flag+: - # - # "+n+":: The client doesn't support channel binding. - # "+y+":: The client does support channel binding - # but thinks the server does not. - # "+p+":: The client requires channel binding. - # The selected channel binding follows "+p=+". - # - # The default always returns "+n+". A mechanism that supports channel - # binding must override this method. - # - def gs2_cb_flag; "n" end - - # The {RFC5801 §4}[https://www.rfc-editor.org/rfc/rfc5801#section-4] - # +gs2-authzid+ header, when +#authzid+ is not empty. - # - # If +#authzid+ is empty or +nil+, an empty string is returned. - def gs2_authzid - return "" if authzid.nil? || authzid == "" - "a=#{gs2_saslname_encode(authzid)}" - end - - module_function - - # Encodes +str+ to match RFC5801_SASLNAME. - def gs2_saslname_encode(str) - str = str.encode("UTF-8") - # Regexp#match raises "invalid byte sequence" for invalid UTF-8 - NO_NULL_CHARS.match str or - raise ArgumentError, "invalid saslname: %p" % [str] - str - .gsub(?=, "=3D") - .gsub(?,, "=2C") - end - - end - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/login_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/login_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/login_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/login_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -# Authenticator for the "+LOGIN+" SASL mechanism. See Net::IMAP#authenticate. -# -# +LOGIN+ authentication sends the password in cleartext. -# RFC3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable -# cleartext authentication until after TLS has been negotiated. -# RFC8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or -# greater be used for all traffic, and deprecate cleartext access ASAP. +LOGIN+ -# can be secured by TLS encryption. -# -# == Deprecated -# -# The {SASL mechanisms -# registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] -# marks "LOGIN" as obsoleted in favor of "PLAIN". It is included here for -# compatibility with existing servers. See -# {draft-murchison-sasl-login}[https://www.iana.org/go/draft-murchison-sasl-login] -# for both specification and deprecation. -class Net::IMAP::SASL::LoginAuthenticator - STATE_USER = :USER - STATE_PASSWORD = :PASSWORD - STATE_DONE = :DONE - private_constant :STATE_USER, :STATE_PASSWORD, :STATE_DONE - - def initialize(user = nil, pass = nil, - authcid: nil, username: nil, - password: nil, secret: nil, - warn_deprecation: true, - **) - if warn_deprecation - warn "WARNING: LOGIN SASL mechanism is deprecated. Use PLAIN instead." - end - @user = authcid || username || user - @password = password || secret || pass - @state = STATE_USER - end - - def initial_response?; false end - - def process(data) - case @state - when STATE_USER - @state = STATE_PASSWORD - return @user - when STATE_PASSWORD - @state = STATE_DONE - return @password - when STATE_DONE - raise ResponseParseError, data - end - end - - def done?; @state == STATE_DONE end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/oauthbearer_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/oauthbearer_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/oauthbearer_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/oauthbearer_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,199 +0,0 @@ -# frozen_string_literal: true - -require_relative "gs2_header" - -module Net - class IMAP < Protocol - module SASL - - # Abstract base class for the SASL mechanisms defined in - # RFC7628[https://tools.ietf.org/html/rfc7628]: - # * OAUTHBEARER[rdoc-ref:OAuthBearerAuthenticator] - # (OAuthBearerAuthenticator) - # * OAUTH10A - class OAuthAuthenticator - include GS2Header - - # Authorization identity: an identity to act as or on behalf of. The - # identity form is application protocol specific. If not provided or - # left blank, the server derives an authorization identity from the - # authentication identity. The server is responsible for verifying the - # client's credentials and verifying that the identity it associates - # with the client's authentication identity is allowed to act as (or on - # behalf of) the authorization identity. - # - # For example, an administrator or superuser might take on another role: - # - # imap.authenticate "PLAIN", "root", passwd, authzid: "user" - # - attr_reader :authzid - alias username authzid - - # Hostname to which the client connected. (optional) - attr_reader :host - - # Service port to which the client connected. (optional) - attr_reader :port - - # HTTP method. (optional) - attr_reader :mthd - - # HTTP path data. (optional) - attr_reader :path - - # HTTP post data. (optional) - attr_reader :post - - # The query string. (optional) - attr_reader :qs - alias query qs - - # Stores the most recent server "challenge". When authentication fails, - # this may hold information about the failure reason, as JSON. - attr_reader :last_server_response - - # Creates an RFC7628[https://tools.ietf.org/html/rfc7628] OAuth - # authenticator. - # - # ==== Parameters - # - # See child classes for required parameter(s). The following parameters - # are all optional, but it is worth noting that application protocols - # are allowed to require #authzid (or other parameters, such as - # #host or #port) as are specific server implementations. - # - # * _optional_ #authzid ― Authorization identity to act as or on behalf of. - # - # _optional_ #username — An alias for #authzid. - # - # Note that, unlike some other authenticators, +username+ sets the - # _authorization_ identity and not the _authentication_ identity. The - # authentication identity is established for the client by the OAuth - # token. - # - # * _optional_ #host — Hostname to which the client connected. - # * _optional_ #port — Service port to which the client connected. - # * _optional_ #mthd — HTTP method - # * _optional_ #path — HTTP path data - # * _optional_ #post — HTTP post data - # * _optional_ #qs — HTTP query string - # - # _optional_ #query — An alias for #qs - # - # Any other keyword parameters are quietly ignored. - def initialize(authzid: nil, host: nil, port: nil, - username: nil, query: nil, - mthd: nil, path: nil, post: nil, qs: nil, **) - @authzid = authzid || username - @host = host - @port = port - @mthd = mthd - @path = path - @post = post - @qs = qs || query - @done = false - end - - # The {RFC7628 §3.1}[https://www.rfc-editor.org/rfc/rfc7628#section-3.1] - # formatted response. - def initial_client_response - kv_pairs = { - host: host, port: port, mthd: mthd, path: path, post: post, qs: qs, - auth: authorization, # authorization is implemented by subclasses - }.compact - [gs2_header, *kv_pairs.map {|kv| kv.join("=") }, "\1"].join("\1") - end - - # Returns initial_client_response the first time, then "^A". - def process(data) - @last_server_response = data - done? ? "\1" : initial_client_response - ensure - @done = true - end - - # Returns true when the initial client response was sent. - # - # The authentication should not succeed unless this returns true, but it - # does *not* indicate success. - def done?; @done end - - # Value of the HTTP Authorization header - # - # Implemented by subclasses. - def authorization; raise "must be implemented by subclass" end - - end - - # Authenticator for the "+OAUTHBEARER+" SASL mechanism, specified in - # RFC7628[https://tools.ietf.org/html/rfc7628]. Authenticates using OAuth - # 2.0 bearer tokens, as described in - # RFC6750[https://tools.ietf.org/html/rfc6750]. Use via - # Net::IMAP#authenticate. - # - # RFC6750[https://tools.ietf.org/html/rfc6750] requires Transport Layer - # Security (TLS) to secure the protocol interaction between the client and - # the resource server. TLS _MUST_ be used for +OAUTHBEARER+ to protect - # the bearer token. - class OAuthBearerAuthenticator < OAuthAuthenticator - - # An OAuth 2.0 bearer token. See {RFC-6750}[https://www.rfc-editor.org/rfc/rfc6750] - attr_reader :oauth2_token - alias secret oauth2_token - - # :call-seq: - # new(oauth2_token, **options) -> authenticator - # new(authzid, oauth2_token, **options) -> authenticator - # new(oauth2_token:, **options) -> authenticator - # - # Creates an Authenticator for the "+OAUTHBEARER+" SASL mechanism. - # - # Called by Net::IMAP#authenticate and similar methods on other clients. - # - # ==== Parameters - # - # * #oauth2_token — An OAuth2 bearer token - # - # All other keyword parameters are passed to - # {super}[rdoc-ref:OAuthAuthenticator::new] (see OAuthAuthenticator). - # The most common ones are: - # - # * _optional_ #authzid ― Authorization identity to act as or on behalf of. - # - # _optional_ #username — An alias for #authzid. - # - # Note that, unlike some other authenticators, +username+ sets the - # _authorization_ identity and not the _authentication_ identity. The - # authentication identity is established for the client by - # #oauth2_token. - # - # * _optional_ #host — Hostname to which the client connected. - # * _optional_ #port — Service port to which the client connected. - # - # Although only oauth2_token is required by this mechanism, it is worth - # noting that application protocols are allowed to - # require #authzid (or other parameters, such as #host - # _or_ #port) as are specific server implementations. - def initialize(arg1 = nil, arg2 = nil, - oauth2_token: nil, secret: nil, - **args, &blk) - username, oauth2_token_arg = arg2.nil? ? [nil, arg1] : [arg1, arg2] - super(username: username, **args, &blk) - @oauth2_token = oauth2_token || secret || oauth2_token_arg or - raise ArgumentError, "missing oauth2_token" - end - - # :call-seq: - # initial_response? -> true - # - # +OAUTHBEARER+ sends an initial client response. - def initial_response?; true end - - # Value of the HTTP Authorization header - def authorization; "Bearer #{oauth2_token}" end - - end - end - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/plain_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/plain_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/plain_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/plain_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -# frozen_string_literal: true - -# Authenticator for the "+PLAIN+" SASL mechanism, specified in -# RFC-4616[https://tools.ietf.org/html/rfc4616]. See Net::IMAP#authenticate. -# -# +PLAIN+ authentication sends the password in cleartext. -# RFC-3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable -# cleartext authentication until after TLS has been negotiated. -# RFC-8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or -# greater be used for all traffic, and deprecate cleartext access ASAP. +PLAIN+ -# can be secured by TLS encryption. -class Net::IMAP::SASL::PlainAuthenticator - - NULL = -"\0".b - private_constant :NULL - - # Authentication identity: the identity that matches the #password. - # - # RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+. - # "Authentication identity" is the generic term used by - # RFC-4422[https://tools.ietf.org/html/rfc4422]. - # RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate - # this to +authcid+. - attr_reader :username - alias authcid username - - # A password or passphrase that matches the #username. - attr_reader :password - alias secret password - - # Authorization identity: an identity to act as or on behalf of. The identity - # form is application protocol specific. If not provided or left blank, the - # server derives an authorization identity from the authentication identity. - # The server is responsible for verifying the client's credentials and - # verifying that the identity it associates with the client's authentication - # identity is allowed to act as (or on behalf of) the authorization identity. - # - # For example, an administrator or superuser might take on another role: - # - # imap.authenticate "PLAIN", "root", passwd, authzid: "user" - # - attr_reader :authzid - - # :call-seq: - # new(username, password, authzid: nil, **) -> authenticator - # new(username:, password:, authzid: nil, **) -> authenticator - # new(authcid:, password:, authzid: nil, **) -> authenticator - # - # Creates an Authenticator for the "+PLAIN+" SASL mechanism. - # - # Called by Net::IMAP#authenticate and similar methods on other clients. - # - # ==== Parameters - # - # * #authcid ― Authentication identity that is associated with #password. - # - # #username ― An alias for #authcid. - # - # * #password ― A password or passphrase associated with the #authcid. - # - # * _optional_ #authzid ― Authorization identity to act as or on behalf of. - # - # When +authzid+ is not set, the server should derive the authorization - # identity from the authentication identity. - # - # Any other keyword parameters are quietly ignored. - def initialize(user = nil, pass = nil, - authcid: nil, secret: nil, - username: nil, password: nil, authzid: nil, **) - username ||= authcid || user or - raise ArgumentError, "missing username (authcid)" - password ||= secret || pass or raise ArgumentError, "missing password" - raise ArgumentError, "username contains NULL" if username.include?(NULL) - raise ArgumentError, "password contains NULL" if password.include?(NULL) - raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL) - @username = username - @password = password - @authzid = authzid - @done = false - end - - # :call-seq: - # initial_response? -> true - # - # +PLAIN+ can send an initial client response. - def initial_response?; true end - - # Responds with the client's credentials. - def process(data) - return "#@authzid\0#@username\0#@password" - ensure - @done = true - end - - # Returns true when the initial client response was sent. - # - # The authentication should not succeed unless this returns true, but it - # does *not* indicate success. - def done?; @done end - -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/protocol_adapters.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/protocol_adapters.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/protocol_adapters.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/protocol_adapters.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - module SASL - - module ProtocolAdapters - # This API is experimental, and may change. - module Generic - def command_name; "AUTHENTICATE" end - def service; raise "Implement in subclass or module" end - def host; client.host end - def port; client.port end - def encode_ir(string) string.empty? ? "=" : encode(string) end - def encode(string) [string].pack("m0") end - def decode(string) string.unpack1("m0") end - def cancel_response; "*" end - end - - # See RFC-3501 (IMAP4rev1), RFC-4959 (SASL-IR capability), - # and RFC-9051 (IMAP4rev2). - module IMAP - include Generic - def service; "imap" end - end - - # See RFC-4954 (AUTH capability). - module SMTP - include Generic - def command_name; "AUTH" end - def service; "smtp" end - end - - # See RFC-5034 (SASL capability). - module POP - include Generic - def command_name; "AUTH" end - def service; "pop" end - end - - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/scram_algorithm.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/scram_algorithm.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/scram_algorithm.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/scram_algorithm.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - module SASL - - # For method descriptions, - # see {RFC5802 §2.2}[https://www.rfc-editor.org/rfc/rfc5802#section-2.2] - # and {RFC5802 §3}[https://www.rfc-editor.org/rfc/rfc5802#section-3]. - module ScramAlgorithm - def Normalize(str) SASL.saslprep(str) end - - def Hi(str, salt, iterations) - length = digest.digest_length - OpenSSL::KDF.pbkdf2_hmac( - str, - salt: salt, - iterations: iterations, - length: length, - hash: digest, - ) - end - - def H(str) digest.digest str end - - def HMAC(key, data) OpenSSL::HMAC.digest(digest, key, data) end - - def XOR(str1, str2) - str1.unpack("C*") - .zip(str2.unpack("C*")) - .map {|a, b| a ^ b } - .pack("C*") - end - - def auth_message - [ - client_first_message_bare, - server_first_message, - client_final_message_without_proof, - ] - .join(",") - end - - def salted_password - Hi(Normalize(password), salt, iterations) - end - - def client_key; HMAC(salted_password, "Client Key") end - def server_key; HMAC(salted_password, "Server Key") end - def stored_key; H(client_key) end - def client_signature; HMAC(stored_key, auth_message) end - def server_signature; HMAC(server_key, auth_message) end - def client_proof; XOR(client_key, client_signature) end - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/scram_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/scram_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/scram_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/scram_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,287 +0,0 @@ -# frozen_string_literal: true - -require "openssl" -require "securerandom" - -require_relative "gs2_header" -require_relative "scram_algorithm" - -module Net - class IMAP - module SASL - - # Abstract base class for the "+SCRAM-*+" family of SASL mechanisms, - # defined in RFC5802[https://tools.ietf.org/html/rfc5802]. Use via - # Net::IMAP#authenticate. - # - # Directly supported: - # * +SCRAM-SHA-1+ --- ScramSHA1Authenticator - # * +SCRAM-SHA-256+ --- ScramSHA256Authenticator - # - # New +SCRAM-*+ mechanisms can easily be added for any hash algorithm - # supported by - # OpenSSL::Digest[https://ruby.github.io/openssl/OpenSSL/Digest.html]. - # Subclasses need only set an appropriate +DIGEST_NAME+ constant. - # - # === SCRAM algorithm - # - # See the documentation and method definitions on ScramAlgorithm for an - # overview of the algorithm. The different mechanisms differ only by - # which hash function that is used (or by support for channel binding with - # +-PLUS+). - # - # See also the methods on GS2Header. - # - # ==== Server messages - # - # As server messages are received, they are validated and loaded into - # the various attributes, e.g: #snonce, #salt, #iterations, #verifier, - # #server_error, etc. - # - # Unlike many other SASL mechanisms, the +SCRAM-*+ family supports mutual - # authentication and can return server error data in the server messages. - # If #process raises an Error for the server-final-message, then - # server_error may contain error details. - # - # === TLS Channel binding - # - # The SCRAM-*-PLUS mechanisms and channel binding are not - # supported yet. - # - # === Caching SCRAM secrets - # - # Caching of salted_password, client_key, stored_key, and server_key - # is not supported yet. - # - class ScramAuthenticator - include GS2Header - include ScramAlgorithm - - # :call-seq: - # new(username, password, **options) -> auth_ctx - # new(username:, password:, **options) -> auth_ctx - # new(authcid:, password:, **options) -> auth_ctx - # - # Creates an authenticator for one of the "+SCRAM-*+" SASL mechanisms. - # Each subclass defines #digest to match a specific mechanism. - # - # Called by Net::IMAP#authenticate and similar methods on other clients. - # - # === Parameters - # - # * #authcid ― Identity whose #password is used. - # - # #username - An alias for #authcid. - # * #password ― Password or passphrase associated with this #username. - # * _optional_ #authzid ― Alternate identity to act as or on behalf of. - # * _optional_ #min_iterations - Overrides the default value (4096). - # - # Any other keyword parameters are quietly ignored. - def initialize(username_arg = nil, password_arg = nil, - authcid: nil, username: nil, - authzid: nil, - password: nil, secret: nil, - min_iterations: 4096, # see both RFC5802 and RFC7677 - cnonce: nil, # must only be set in tests - **options) - @username = username || username_arg || authcid or - raise ArgumentError, "missing username (authcid)" - @password = password || secret || password_arg or - raise ArgumentError, "missing password" - @authzid = authzid - - @min_iterations = Integer min_iterations - @min_iterations.positive? or - raise ArgumentError, "min_iterations must be positive" - - @cnonce = cnonce || SecureRandom.base64(32) - end - - # Authentication identity: the identity that matches the #password. - # - # RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+. - # "Authentication identity" is the generic term used by - # RFC-4422[https://tools.ietf.org/html/rfc4422]. - # RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate - # this to +authcid+. - attr_reader :username - alias authcid username - - # A password or passphrase that matches the #username. - attr_reader :password - alias secret password - - # Authorization identity: an identity to act as or on behalf of. The - # identity form is application protocol specific. If not provided or - # left blank, the server derives an authorization identity from the - # authentication identity. For example, an administrator or superuser - # might take on another role: - # - # imap.authenticate "SCRAM-SHA-256", "root", passwd, authzid: "user" - # - # The server is responsible for verifying the client's credentials and - # verifying that the identity it associates with the client's - # authentication identity is allowed to act as (or on behalf of) the - # authorization identity. - attr_reader :authzid - - # The minimal allowed iteration count. Lower #iterations will raise an - # Error. - attr_reader :min_iterations - - # The client nonce, generated by SecureRandom - attr_reader :cnonce - - # The server nonce, which must start with #cnonce - attr_reader :snonce - - # The salt used by the server for this user - attr_reader :salt - - # The iteration count for the selected hash function and user - attr_reader :iterations - - # An error reported by the server during the \SASL exchange. - # - # Does not include errors reported by the protocol, e.g. - # Net::IMAP::NoResponseError. - attr_reader :server_error - - # Returns a new OpenSSL::Digest object, set to the appropriate hash - # function for the chosen mechanism. - # - # The class's +DIGEST_NAME+ constant must be set to the name of an - # algorithm supported by OpenSSL::Digest. - def digest; OpenSSL::Digest.new self.class::DIGEST_NAME end - - # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] - # +client-first-message+. - def initial_client_response - "#{gs2_header}#{client_first_message_bare}" - end - - # responds to the server's challenges - def process(challenge) - case (@state ||= :initial_client_response) - when :initial_client_response - initial_client_response.tap { @state = :server_first_message } - when :server_first_message - recv_server_first_message challenge - final_message_with_proof.tap { @state = :server_final_message } - when :server_final_message - recv_server_final_message challenge - "".tap { @state = :done } - else - raise Error, "server sent after complete, %p" % [challenge] - end - rescue Exception => ex - @state = ex - raise - end - - # Is the authentication exchange complete? - # - # If false, another server continuation is required. - def done?; @state == :done end - - private - - # Need to store this for auth_message - attr_reader :server_first_message - - def format_message(hash) hash.map { _1.join("=") }.join(",") end - - def recv_server_first_message(server_first_message) - @server_first_message = server_first_message - sparams = parse_challenge server_first_message - @snonce = sparams["r"] or - raise Error, "server did not send nonce" - @salt = sparams["s"]&.unpack1("m") or - raise Error, "server did not send salt" - @iterations = sparams["i"]&.then {|i| Integer i } or - raise Error, "server did not send iteration count" - min_iterations <= iterations or - raise Error, "too few iterations: %d" % [iterations] - mext = sparams["m"] and - raise Error, "mandatory extension: %p" % [mext] - snonce.start_with? cnonce or - raise Error, "invalid server nonce" - end - - def recv_server_final_message(server_final_message) - sparams = parse_challenge server_final_message - @server_error = sparams["e"] and - raise Error, "server error: %s" % [server_error] - verifier = sparams["v"].unpack1("m") or - raise Error, "server did not send verifier" - verifier == server_signature or - raise Error, "server verify failed: %p != %p" % [ - server_signature, verifier - ] - end - - # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] - # +client-first-message-bare+. - def client_first_message_bare - @client_first_message_bare ||= - format_message(n: gs2_saslname_encode(SASL.saslprep(username)), - r: cnonce) - end - - # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] - # +client-final-message+. - def final_message_with_proof - proof = [client_proof].pack("m0") - "#{client_final_message_without_proof},p=#{proof}" - end - - # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] - # +client-final-message-without-proof+. - def client_final_message_without_proof - @client_final_message_without_proof ||= - format_message(c: [cbind_input].pack("m0"), # channel-binding - r: snonce) # nonce - end - - # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] - # +cbind-input+. - # - # >>> - # *TODO:* implement channel binding, appending +cbind-data+ here. - alias cbind_input gs2_header - - # RFC5802 specifies "that the order of attributes in client or server - # messages is fixed, with the exception of extension attributes", but - # this parses it simply as a hash, without respect to order. Note that - # repeated keys (violating the spec) will use the last value. - def parse_challenge(challenge) - challenge.split(/,/).to_h {|pair| pair.split(/=/, 2) } - rescue ArgumentError - raise Error, "unparsable challenge: %p" % [challenge] - end - - end - - # Authenticator for the "+SCRAM-SHA-1+" SASL mechanism, defined in - # RFC5802[https://tools.ietf.org/html/rfc5802]. - # - # Uses the "SHA-1" digest algorithm from OpenSSL::Digest. - # - # See ScramAuthenticator. - class ScramSHA1Authenticator < ScramAuthenticator - DIGEST_NAME = "SHA1" - end - - # Authenticator for the "+SCRAM-SHA-256+" SASL mechanism, defined in - # RFC7677[https://tools.ietf.org/html/rfc7677]. - # - # Uses the "SHA-256" digest algorithm from OpenSSL::Digest. - # - # See ScramAuthenticator. - class ScramSHA256Authenticator < ScramAuthenticator - DIGEST_NAME = "SHA256" - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/stringprep.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/stringprep.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/stringprep.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/stringprep.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Net::IMAP::SASL - - # Alias for Net::IMAP::StringPrep::SASLprep. - SASLprep = Net::IMAP::StringPrep::SASLprep - StringPrep = Net::IMAP::StringPrep # :nodoc: - BidiStringError = Net::IMAP::StringPrep::BidiStringError # :nodoc: - ProhibitedCodepoint = Net::IMAP::StringPrep::ProhibitedCodepoint # :nodoc: - StringPrepError = Net::IMAP::StringPrep::StringPrepError # :nodoc: - -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/xoauth2_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/xoauth2_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/xoauth2_authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl/xoauth2_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,106 +0,0 @@ -# frozen_string_literal: true - -# Authenticator for the "+XOAUTH2+" SASL mechanism. This mechanism was -# originally created for GMail and widely adopted by hosted email providers. -# +XOAUTH2+ has been documented by -# Google[https://developers.google.com/gmail/imap/xoauth2-protocol] and -# Microsoft[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth]. -# -# This mechanism requires an OAuth2 access token which has been authorized -# with the appropriate OAuth2 scopes to access the user's services. Most of -# these scopes are not standardized---consult each service provider's -# documentation for their scopes. -# -# Although this mechanism was never standardized and has been obsoleted by -# "+OAUTHBEARER+", it is still very widely supported. -# -# See Net::IMAP::SASL::OAuthBearerAuthenticator. -class Net::IMAP::SASL::XOAuth2Authenticator - - # It is unclear from {Google's original XOAUTH2 - # documentation}[https://developers.google.com/gmail/imap/xoauth2-protocol], - # whether "User" refers to the authentication identity (+authcid+) or the - # authorization identity (+authzid+). The authentication identity is - # established for the client by the OAuth token, so it seems that +username+ - # must be the authorization identity. - # - # {Microsoft's documentation for shared - # mailboxes}[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#sasl-xoauth2-authentication-for-shared-mailboxes-in-office-365] - # _clearly_ indicates that the Office 365 server interprets it as the - # authorization identity. - # - # Although they _should_ validate that the token has been authorized to access - # the service for +username+, _some_ servers appear to ignore this field, - # relying only the identity and scope authorized by the token. - attr_reader :username - - # Note that, unlike most other authenticators, #username is an alias for the - # authorization identity and not the authentication identity. The - # authenticated identity is established for the client by the #oauth2_token. - alias authzid username - - # An OAuth2 access token which has been authorized with the appropriate OAuth2 - # scopes to use the service for #username. - attr_reader :oauth2_token - alias secret oauth2_token - - # :call-seq: - # new(username, oauth2_token, **) -> authenticator - # new(username:, oauth2_token:, **) -> authenticator - # new(authzid:, oauth2_token:, **) -> authenticator - # - # Creates an Authenticator for the "+XOAUTH2+" SASL mechanism, as specified by - # Google[https://developers.google.com/gmail/imap/xoauth2-protocol], - # Microsoft[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth] - # and Yahoo[https://senders.yahooinc.com/developer/documentation]. - # - # === Properties - # - # * #username --- the username for the account being accessed. - # - # #authzid --- an alias for #username. - # - # Note that, unlike some other authenticators, +username+ sets the - # _authorization_ identity and not the _authentication_ identity. The - # authenticated identity is established for the client with the OAuth token. - # - # * #oauth2_token --- An OAuth2.0 access token which is authorized to access - # the service for #username. - # - # Any other keyword parameters are quietly ignored. - def initialize(user = nil, token = nil, username: nil, oauth2_token: nil, - authzid: nil, secret: nil, **) - @username = authzid || username || user or - raise ArgumentError, "missing username (authzid)" - @oauth2_token = oauth2_token || secret || token or - raise ArgumentError, "missing oauth2_token" - @done = false - end - - # :call-seq: - # initial_response? -> true - # - # +XOAUTH2+ can send an initial client response. - def initial_response?; true end - - # Returns the XOAUTH2 formatted response, which combines the +username+ - # with the +oauth2_token+. - def process(_data) - build_oauth2_string(@username, @oauth2_token) - ensure - @done = true - end - - # Returns true when the initial client response was sent. - # - # The authentication should not succeed unless this returns true, but it - # does *not* indicate success. - def done?; @done end - - private - - def build_oauth2_string(username, oauth2_token) - format("user=%s\1auth=Bearer %s\1\1", username, oauth2_token) - end - -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,179 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - - # Pluggable authentication mechanisms for protocols which support SASL - # (Simple Authentication and Security Layer), such as IMAP4, SMTP, LDAP, and - # XMPP. {RFC-4422}[https://tools.ietf.org/html/rfc4422] specifies the - # common \SASL framework: - # >>> - # SASL is conceptually a framework that provides an abstraction layer - # between protocols and mechanisms as illustrated in the following - # diagram. - # - # SMTP LDAP XMPP Other protocols ... - # \ | | / - # \ | | / - # SASL abstraction layer - # / | | \ - # / | | \ - # EXTERNAL GSSAPI PLAIN Other mechanisms ... - # - # Net::IMAP uses SASL via the Net::IMAP#authenticate method. - # - # == Mechanisms - # - # Each mechanism has different properties and requirements. Please consult - # the documentation for the specific mechanisms you are using: - # - # +ANONYMOUS+:: - # See AnonymousAuthenticator. - # - # Allows the user to gain access to public services or resources without - # authenticating or disclosing an identity. - # - # +EXTERNAL+:: - # See ExternalAuthenticator. - # - # Authenticates using already established credentials, such as a TLS - # certificate or IPsec. - # - # +OAUTHBEARER+:: - # See OAuthBearerAuthenticator. - # - # Login using an OAuth2 Bearer token. This is the standard mechanism - # for using OAuth2 with \SASL, but it is not yet deployed as widely as - # +XOAUTH2+. - # - # +PLAIN+:: - # See PlainAuthenticator. - # - # Login using clear-text username and password. - # - # +SCRAM-SHA-1+:: - # +SCRAM-SHA-256+:: - # See ScramAuthenticator. - # - # Login by username and password. The password is not sent to the - # server but is used in a salted challenge/response exchange. - # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by - # Net::IMAP::SASL. New authenticators can easily be added for any other - # SCRAM-* mechanism if the digest algorithm is supported by - # OpenSSL::Digest. - # - # +XOAUTH2+:: - # See XOAuth2Authenticator. - # - # Login using a username and an OAuth2 access token. Non-standard and - # obsoleted by +OAUTHBEARER+, but widely supported. - # - # See the {SASL mechanism - # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] - # for a list of all SASL mechanisms and their specifications. To register - # new authenticators, see Authenticators. - # - # === Deprecated mechanisms - # - # Obsolete mechanisms should be avoided, but are still available for - # backwards compatibility. - # - # >>> - # For +DIGEST-MD5+ see DigestMD5Authenticator. - # - # For +LOGIN+, see LoginAuthenticator. - # - # For +CRAM-MD5+, see CramMD5Authenticator. - # - # Using a deprecated mechanism will print a warning. - # - module SASL - # Exception class for any client error detected during the authentication - # exchange. - # - # When the _server_ reports an authentication failure, it will respond - # with a protocol specific error instead, e.g: +BAD+ or +NO+ in IMAP. - # - # When the client encounters any error, it *must* consider the - # authentication exchange to be unsuccessful and it might need to drop the - # connection. For example, if the server reports that the authentication - # exchange was successful or the protocol does not allow additional - # authentication attempts. - Error = Class.new(StandardError) - - # Indicates an authentication exchange that will be or has been canceled - # by the client, not due to any error or failure during processing. - AuthenticationCanceled = Class.new(Error) - - # Indicates an error when processing a server challenge, e.g: an invalid - # or unparsable challenge. An underlying exception may be available as - # the exception's #cause. - AuthenticationError = Class.new(Error) - - # Indicates that authentication cannot proceed because one of the server's - # messages has not passed integrity checks. - AuthenticationFailed = Class.new(Error) - - # Indicates that authentication cannot proceed because one of the server's - # ended authentication prematurely. - class AuthenticationIncomplete < AuthenticationFailed - # The success response from the server - attr_reader :response - - def initialize(response, message = "authentication ended prematurely") - super(message) - @response = response - end - end - - # autoloading to avoid loading all of the regexps when they aren't used. - sasl_stringprep_rb = File.expand_path("sasl/stringprep", __dir__) - autoload :StringPrep, sasl_stringprep_rb - autoload :SASLprep, sasl_stringprep_rb - autoload :StringPrepError, sasl_stringprep_rb - autoload :ProhibitedCodepoint, sasl_stringprep_rb - autoload :BidiStringError, sasl_stringprep_rb - - sasl_dir = File.expand_path("sasl", __dir__) - autoload :AuthenticationExchange, "#{sasl_dir}/authentication_exchange" - autoload :ClientAdapter, "#{sasl_dir}/client_adapter" - autoload :ProtocolAdapters, "#{sasl_dir}/protocol_adapters" - - autoload :Authenticators, "#{sasl_dir}/authenticators" - autoload :GS2Header, "#{sasl_dir}/gs2_header" - autoload :ScramAlgorithm, "#{sasl_dir}/scram_algorithm" - - autoload :AnonymousAuthenticator, "#{sasl_dir}/anonymous_authenticator" - autoload :ExternalAuthenticator, "#{sasl_dir}/external_authenticator" - autoload :OAuthBearerAuthenticator, "#{sasl_dir}/oauthbearer_authenticator" - autoload :PlainAuthenticator, "#{sasl_dir}/plain_authenticator" - autoload :ScramAuthenticator, "#{sasl_dir}/scram_authenticator" - autoload :ScramSHA1Authenticator, "#{sasl_dir}/scram_authenticator" - autoload :ScramSHA256Authenticator, "#{sasl_dir}/scram_authenticator" - autoload :XOAuth2Authenticator, "#{sasl_dir}/xoauth2_authenticator" - - autoload :CramMD5Authenticator, "#{sasl_dir}/cram_md5_authenticator" - autoload :DigestMD5Authenticator, "#{sasl_dir}/digest_md5_authenticator" - autoload :LoginAuthenticator, "#{sasl_dir}/login_authenticator" - - # Returns the default global SASL::Authenticators instance. - def self.authenticators; @authenticators ||= Authenticators.new end - - # Delegates to registry.new See Authenticators#new. - def self.authenticator(*args, registry: authenticators, **kwargs, &block) - registry.new(*args, **kwargs, &block) - end - - # Delegates to ::authenticators. See Authenticators#add_authenticator. - def self.add_authenticator(...) authenticators.add_authenticator(...) end - - module_function - - # See Net::IMAP::StringPrep::SASLprep#saslprep. - def saslprep(string, **opts) - Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts) - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl_adapter.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl_adapter.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl_adapter.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sasl_adapter.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - - # Experimental - class SASLAdapter < SASL::ClientAdapter - include SASL::ProtocolAdapters::IMAP - - RESPONSE_ERRORS = [NoResponseError, BadResponseError, ByeResponseError] - .freeze - - def response_errors; RESPONSE_ERRORS end - def sasl_ir_capable?; client.capable?("SASL-IR") end - def auth_capable?(mechanism); client.auth_capable?(mechanism) end - def drop_connection; client.logout! end - def drop_connection!; client.disconnect end - end - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/search_result.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/search_result.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/search_result.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/search_result.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,150 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - - # An array of sequence numbers returned by Net::IMAP#search, or unique - # identifiers returned by Net::IMAP#uid_search. - # - # For backward compatibility, SearchResult inherits from Array. - class SearchResult < Array - - # Returns a frozen SearchResult populated with the given +seq_nums+. - # - # Net::IMAP::SearchResult[1, 3, 5, modseq: 9] - # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9] - def self.[](*seq_nums, modseq: nil) - new(seq_nums, modseq: modseq) - end - - # A modification sequence number, as described by the +CONDSTORE+ - # extension in {[RFC7162 - # §3.1.6]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.6]. - attr_reader :modseq - - # Returns a frozen SearchResult populated with the given +seq_nums+. - # - # Net::IMAP::SearchResult.new([1, 3, 5], modseq: 9) - # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9] - def initialize(seq_nums, modseq: nil) - super(seq_nums.to_ary.map { Integer _1 }) - @modseq = Integer modseq if modseq - freeze - end - - # Returns a frozen copy of +other+. - def initialize_copy(other); super; freeze end - - # Returns whether +other+ is a SearchResult with the same values and the - # same #modseq. The order of numbers is irrelevant. - # - # Net::IMAP::SearchResult[123, 456, modseq: 789] == - # Net::IMAP::SearchResult[123, 456, modseq: 789] - # # => true - # Net::IMAP::SearchResult[123, 456, modseq: 789] == - # Net::IMAP::SearchResult[456, 123, modseq: 789] - # # => true - # - # Net::IMAP::SearchResult[123, 456, modseq: 789] == - # Net::IMAP::SearchResult[987, 654, modseq: 789] - # # => false - # Net::IMAP::SearchResult[123, 456, modseq: 789] == - # Net::IMAP::SearchResult[1, 2, 3, modseq: 9999] - # # => false - # - # SearchResult can be compared directly with Array, if #modseq is nil and - # the array is sorted. - # - # Net::IMAP::SearchResult[9, 8, 6, 4, 1] == [1, 4, 6, 8, 9] # => true - # Net::IMAP::SearchResult[3, 5, 7, modseq: 99] == [3, 5, 7] # => false - # - # Note that Array#== does require matching order and ignores #modseq. - # - # [9, 8, 6, 4, 1] == Net::IMAP::SearchResult[1, 4, 6, 8, 9] # => false - # [3, 5, 7] == Net::IMAP::SearchResult[3, 5, 7, modseq: 99] # => true - # - def ==(other) - (modseq ? - other.is_a?(self.class) && modseq == other.modseq : - other.is_a?(Array)) && - size == other.size && - sort == other.sort - end - - # Hash equality. Unlike #==, order will be taken into account. - def hash - return super if modseq.nil? - [super, self.class, modseq].hash - end - - # Hash equality. Unlike #==, order will be taken into account. - def eql?(other) - return super if modseq.nil? - self.class == other.class && hash == other.hash - end - - # Returns a string that represents the SearchResult. - # - # Net::IMAP::SearchResult[123, 456, 789].inspect - # # => "[123, 456, 789]" - # - # Net::IMAP::SearchResult[543, 210, 678, modseq: 2048].inspect - # # => "Net::IMAP::SearchResult[543, 210, 678, modseq: 2048]" - # - def inspect - return super if modseq.nil? - "%s[%s, modseq: %p]" % [self.class, join(", "), modseq] - end - - # Returns a string that follows the formal \IMAP syntax. - # - # data = Net::IMAP::SearchResult[2, 8, 32, 128, 256, 512] - # data.to_s # => "* SEARCH 2 8 32 128 256 512" - # data.to_s("SEARCH") # => "* SEARCH 2 8 32 128 256 512" - # data.to_s("SORT") # => "* SORT 2 8 32 128 256 512" - # data.to_s(nil) # => "2 8 32 128 256 512" - # - # data = Net::IMAP::SearchResult[1, 3, 16, 1024, modseq: 2048].to_s - # data.to_s # => "* SEARCH 1 3 16 1024 (MODSEQ 2048)" - # data.to_s("SORT") # => "* SORT 1 3 16 1024 (MODSEQ 2048)" - # data.to_s # => "1 3 16 1024 (MODSEQ 2048)" - # - def to_s(type = "SEARCH") - str = +"" - str << "* %s " % [type.to_str] unless type.nil? - str << join(" ") - str << " (MODSEQ %d)" % [modseq] if modseq - -str - end - - # Converts the SearchResult into a SequenceSet. - # - # Net::IMAP::SearchResult[9, 1, 2, 4, 10, 12, 3, modseq: 123_456] - # .to_sequence_set - # # => Net::IMAP::SequenceSet["1:4,9:10,12"] - def to_sequence_set; SequenceSet[*self] end - - def pretty_print(pp) - return super if modseq.nil? - pp.text self.class.name + "[" - pp.group_sub do - pp.nest(2) do - pp.breakable "" - each do |num| - pp.pp num - pp.text "," - pp.fill_breakable - end - pp.breakable "" - pp.text "modseq: " - pp.pp modseq - end - pp.breakable "" - pp.text "]" - end - end - - end - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sequence_set.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sequence_set.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/sequence_set.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/sequence_set.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,1414 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - - ## - # An \IMAP sequence set is a set of message sequence numbers or unique - # identifier numbers ("UIDs"). It contains numbers and ranges of numbers. - # The numbers are all non-zero unsigned 32-bit integers and one special - # value ("*") that represents the largest value in the mailbox. - # - # Certain types of \IMAP responses will contain a SequenceSet, for example - # the data for a "MODIFIED" ResponseCode. Some \IMAP commands may - # receive a SequenceSet as an argument, for example IMAP#search, IMAP#fetch, - # and IMAP#store. - # - # == EXPERIMENTAL API - # - # SequenceSet is currently experimental. Only two methods, ::[] and - # #valid_string, are considered stable. Although the API isn't expected to - # change much, any other methods may be removed or changed without - # deprecation. - # - # == Creating sequence sets - # - # SequenceSet.new with no arguments creates an empty sequence set. Note - # that an empty sequence set is invalid in the \IMAP grammar. - # - # set = Net::IMAP::SequenceSet.new - # set.empty? #=> true - # set.valid? #=> false - # set.valid_string #!> raises DataFormatError - # set << 1..10 - # set.empty? #=> false - # set.valid? #=> true - # set.valid_string #=> "1:10" - # - # SequenceSet.new may receive a single optional argument: a non-zero 32 bit - # unsigned integer, a range, a sequence-set formatted string, - # another sequence set, or an enumerable containing any of these. - # - # set = Net::IMAP::SequenceSet.new(1) - # set.valid_string #=> "1" - # set = Net::IMAP::SequenceSet.new(1..100) - # set.valid_string #=> "1:100" - # set = Net::IMAP::SequenceSet.new(1...100) - # set.valid_string #=> "1:99" - # set = Net::IMAP::SequenceSet.new([1, 2, 5..]) - # set.valid_string #=> "1:2,5:*" - # set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024") - # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024" - # set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024) - # set.valid_string #=> "1:10,55,1024:2048" - # - # Use ::[] with one or more arguments to create a frozen SequenceSet. An - # invalid (empty) set cannot be created with ::[]. - # - # set = Net::IMAP::SequenceSet["1,2,3:7,5,6:10,2048,1024"] - # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024" - # set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024] - # set.valid_string #=> "1:10,55,1024:2048" - # - # == Normalized form - # - # When a sequence set is created with a single String value, that #string - # representation is preserved. SequenceSet's internal representation - # implicitly sorts all entries, de-duplicates numbers, and coalesces - # adjacent or overlapping ranges. Most enumeration methods and offset-based - # methods use this normalized representation. Most modification methods - # will convert #string to its normalized form. - # - # In some cases the order of the string representation is significant, such - # as the +ESORT+, CONTEXT=SORT, and +UIDPLUS+ extensions. Use - # #entries or #each_entry to enumerate the set in its original order. To - # preserve #string order while modifying a set, use #append, #string=, or - # #replace. - # - # == Using * - # - # \IMAP sequence sets may contain a special value "*", which - # represents the largest number in use. From +seq-number+ in - # {RFC9051 §9}[https://www.rfc-editor.org/rfc/rfc9051.html#section-9-5]: - # >>> - # In the case of message sequence numbers, it is the number of messages - # in a non-empty mailbox. In the case of unique identifiers, it is the - # unique identifier of the last message in the mailbox or, if the - # mailbox is empty, the mailbox's current UIDNEXT value. - # - # When creating a SequenceSet, * may be input as -1, - # "*", :*, an endless range, or a range ending in - # -1. When converting to #elements, #ranges, or #numbers, it will - # output as either :* or an endless range. For example: - # - # Net::IMAP::SequenceSet["1,3,*"].to_a #=> [1, 3, :*] - # Net::IMAP::SequenceSet["1,234:*"].to_a #=> [1, 234..] - # Net::IMAP::SequenceSet[1234..-1].to_a #=> [1234..] - # Net::IMAP::SequenceSet[1234..].to_a #=> [1234..] - # - # Net::IMAP::SequenceSet[1234..].to_s #=> "1234:*" - # Net::IMAP::SequenceSet[1234..-1].to_s #=> "1234:*" - # - # Use #limit to convert "*" to a maximum value. When a range - # includes "*", the maximum value will always be matched: - # - # Net::IMAP::SequenceSet["9999:*"].limit(max: 25) - # #=> Net::IMAP::SequenceSet["25"] - # - # === Surprising * behavior - # - # When a set includes *, some methods may have surprising behavior. - # - # For example, #complement treats * as its own number. This way, - # the #intersection of a set and its #complement will always be empty. - # This is not how an \IMAP server interprets the set: it will convert - # * to either the number of messages in the mailbox or +UIDNEXT+, - # as appropriate. And there _will_ be overlap between a set and its - # complement after #limit is applied to each: - # - # ~Net::IMAP::SequenceSet["*"] == Net::IMAP::SequenceSet[1..(2**32-1)] - # ~Net::IMAP::SequenceSet[1..5] == Net::IMAP::SequenceSet["6:*"] - # - # set = Net::IMAP::SequenceSet[1..5] - # (set & ~set).empty? => true - # - # (set.limit(max: 4) & (~set).limit(max: 4)).to_a => [4] - # - # When counting the number of numbers in a set, * will be counted - # _except_ when UINT32_MAX is also in the set: - # UINT32_MAX = 2**32 - 1 - # Net::IMAP::SequenceSet["*"].count => 1 - # Net::IMAP::SequenceSet[1..UINT32_MAX - 1, :*].count => UINT32_MAX - # - # Net::IMAP::SequenceSet["1:*"].count => UINT32_MAX - # Net::IMAP::SequenceSet[UINT32_MAX, :*].count => 1 - # Net::IMAP::SequenceSet[UINT32_MAX..].count => 1 - # - # == What's here? - # - # SequenceSet provides methods for: - # * {Creating a SequenceSet}[rdoc-ref:SequenceSet@Methods+for+Creating+a+SequenceSet] - # * {Comparing}[rdoc-ref:SequenceSet@Methods+for+Comparing] - # * {Querying}[rdoc-ref:SequenceSet@Methods+for+Querying] - # * {Iterating}[rdoc-ref:SequenceSet@Methods+for+Iterating] - # * {Set Operations}[rdoc-ref:SequenceSet@Methods+for+Set+Operations] - # * {Assigning}[rdoc-ref:SequenceSet@Methods+for+Assigning] - # * {Deleting}[rdoc-ref:SequenceSet@Methods+for+Deleting] - # * {IMAP String Formatting}[rdoc-ref:SequenceSet@Methods+for+IMAP+String+Formatting] - # - # === Methods for Creating a \SequenceSet - # * ::[]: Creates a validated frozen sequence set from one or more inputs. - # * ::new: Creates a new mutable sequence set, which may be empty (invalid). - # * ::try_convert: Calls +to_sequence_set+ on an object and verifies that - # the result is a SequenceSet. - # * ::empty: Returns a frozen empty (invalid) SequenceSet. - # * ::full: Returns a frozen SequenceSet containing every possible number. - # - # === Methods for Comparing - # - # Comparison to another \SequenceSet: - # - #==: Returns whether a given set contains the same numbers as +self+. - # - #eql?: Returns whether a given set uses the same #string as +self+. - # - # Comparison to objects which are convertible to \SequenceSet: - # - #===: - # Returns whether a given object is fully contained within +self+, or - # +nil+ if the object cannot be converted to a compatible type. - # - #cover? (aliased as #===): - # Returns whether a given object is fully contained within +self+. - # - #intersect? (aliased as #overlap?): - # Returns whether +self+ and a given object have any common elements. - # - #disjoint?: - # Returns whether +self+ and a given object have no common elements. - # - # === Methods for Querying - # These methods do not modify +self+. - # - # Set membership: - # - #include? (aliased as #member?): - # Returns whether a given object (nz-number, range, or *) is - # contained by the set. - # - #include_star?: Returns whether the set contains *. - # - # Minimum and maximum value elements: - # - #min: Returns the minimum number in the set. - # - #max: Returns the maximum number in the set. - # - #minmax: Returns the minimum and maximum numbers in the set. - # - # Accessing value by offset: - # - #[] (aliased as #slice): Returns the number or consecutive subset at a - # given offset or range of offsets. - # - #at: Returns the number at a given offset. - # - #find_index: Returns the given number's offset in the set - # - # Set cardinality: - # - #count (aliased as #size): Returns the count of numbers in the set. - # - #empty?: Returns whether the set has no members. \IMAP syntax does not - # allow empty sequence sets. - # - #valid?: Returns whether the set has any members. - # - #full?: Returns whether the set contains every possible value, including - # *. - # - # === Methods for Iterating - # - # - #each_element: Yields each number and range in the set, sorted and - # coalesced, and returns +self+. - # - #elements (aliased as #to_a): Returns an Array of every number and range - # in the set, sorted and coalesced. - # - #each_entry: Yields each number and range in the set, unsorted and - # without deduplicating numbers or coalescing ranges, and returns +self+. - # - #entries: Returns an Array of every number and range in the set, - # unsorted and without deduplicating numbers or coalescing ranges. - # - #each_range: - # Yields each element in the set as a Range and returns +self+. - # - #ranges: Returns an Array of every element in the set, converting - # numbers into ranges of a single value. - # - #each_number: Yields each number in the set and returns +self+. - # - #numbers: Returns an Array with every number in the set, expanding - # ranges into all of their contained numbers. - # - #to_set: Returns a Set containing all of the #numbers in the set. - # - # === Methods for \Set Operations - # These methods do not modify +self+. - # - # - #| (aliased as #union and #+): Returns a new set combining all members - # from +self+ with all members from the other object. - # - #& (aliased as #intersection): Returns a new set containing all members - # common to +self+ and the other object. - # - #- (aliased as #difference): Returns a copy of +self+ with all members - # in the other object removed. - # - #^ (aliased as #xor): Returns a new set containing all members from - # +self+ and the other object except those common to both. - # - #~ (aliased as #complement): Returns a new set containing all members - # that are not in +self+ - # - #limit: Returns a copy of +self+ which has replaced * with a - # given maximum value and removed all members over that maximum. - # - # === Methods for Assigning - # These methods add or replace elements in +self+. - # - # - #add (aliased as #<<): Adds a given object to the set; returns +self+. - # - #add?: If the given object is not an element in the set, adds it and - # returns +self+; otherwise, returns +nil+. - # - #merge: Merges multiple elements into the set; returns +self+. - # - #append: Adds a given object to the set, appending it to the existing - # string, and returns +self+. - # - #string=: Assigns a new #string value and replaces #elements to match. - # - #replace: Replaces the contents of the set with the contents - # of a given object. - # - #complement!: Replaces the contents of the set with its own #complement. - # - # === Methods for Deleting - # These methods remove elements from +self+. - # - # - #clear: Removes all elements in the set; returns +self+. - # - #delete: Removes a given object from the set; returns +self+. - # - #delete?: If the given object is an element in the set, removes it and - # returns it; otherwise, returns +nil+. - # - #delete_at: Removes the number at a given offset. - # - #slice!: Removes the number or consecutive numbers at a given offset or - # range of offsets. - # - #subtract: Removes each given object from the set; returns +self+. - # - #limit!: Replaces * with a given maximum value and removes all - # members over that maximum; returns +self+. - # - # === Methods for \IMAP String Formatting - # - # - #to_s: Returns the +sequence-set+ string, or an empty string when the - # set is empty. - # - #string: Returns the +sequence-set+ string, or nil when empty. - # - #valid_string: Returns the +sequence-set+ string, or raises - # DataFormatError when the set is empty. - # - #normalized_string: Returns a sequence-set string with its - # elements sorted and coalesced, or nil when the set is empty. - # - #normalize: Returns a new set with this set's normalized +sequence-set+ - # representation. - # - #normalize!: Updates #string to its normalized +sequence-set+ - # representation and returns +self+. - # - class SequenceSet - # The largest possible non-zero unsigned 32-bit integer - UINT32_MAX = 2**32 - 1 - - # represents "*" internally, to simplify sorting (etc) - STAR_INT = UINT32_MAX + 1 - private_constant :STAR_INT - - # valid inputs for "*" - STARS = [:*, ?*, -1].freeze - private_constant :STAR_INT, :STARS - - COERCIBLE = ->{ _1.respond_to? :to_sequence_set } - ENUMABLE = ->{ _1.respond_to?(:each) && _1.respond_to?(:empty?) } - private_constant :COERCIBLE, :ENUMABLE - - class << self - - # :call-seq: - # SequenceSet[*values] -> valid frozen sequence set - # - # Returns a frozen SequenceSet, constructed from +values+. - # - # An empty SequenceSet is invalid and will raise a DataFormatError. - # - # Use ::new to create a mutable or empty SequenceSet. - def [](first, *rest) - if rest.empty? - if first.is_a?(SequenceSet) && set.frozen? && set.valid? - first - else - new(first).validate.freeze - end - else - new(first).merge(*rest).validate.freeze - end - end - - # :call-seq: - # SequenceSet.try_convert(obj) -> sequence set or nil - # - # If +obj+ is a SequenceSet, returns +obj+. If +obj+ responds_to - # +to_sequence_set+, calls +obj.to_sequence_set+ and returns the result. - # Otherwise returns +nil+. - # - # If +obj.to_sequence_set+ doesn't return a SequenceSet, an exception is - # raised. - def try_convert(obj) - return obj if obj.is_a?(SequenceSet) - return nil unless respond_to?(:to_sequence_set) - obj = obj.to_sequence_set - return obj if obj.is_a?(SequenceSet) - raise DataFormatError, "invalid object returned from to_sequence_set" - end - - # Returns a frozen empty set singleton. Note that valid \IMAP sequence - # sets cannot be empty, so this set is _invalid_. - def empty; EMPTY end - - # Returns a frozen full set singleton: "1:*" - def full; FULL end - - end - - # Create a new SequenceSet object from +input+, which may be another - # SequenceSet, an IMAP formatted +sequence-set+ string, a number, a - # range, :*, or an enumerable of these. - # - # Use ::[] to create a frozen (non-empty) SequenceSet. - def initialize(input = nil) input ? replace(input) : clear end - - # Removes all elements and returns self. - def clear; @tuples, @string = [], nil; self end - - # Replace the contents of the set with the contents of +other+ and returns - # +self+. - # - # +other+ may be another SequenceSet, or it may be an IMAP +sequence-set+ - # string, a number, a range, *, or an enumerable of these. - def replace(other) - case other - when SequenceSet then initialize_dup(other) - when String then self.string = other - else clear; merge other - end - self - end - - # Returns the \IMAP +sequence-set+ string representation, or raises a - # DataFormatError when the set is empty. - # - # Use #string to return +nil+ or #to_s to return an empty string without - # error. - # - # Related: #string, #normalized_string, #to_s - def valid_string - raise DataFormatError, "empty sequence-set" if empty? - string - end - - # Returns the \IMAP +sequence-set+ string representation, or +nil+ when - # the set is empty. Note that an empty set is invalid in the \IMAP - # syntax. - # - # Use #valid_string to raise an exception when the set is empty, or #to_s - # to return an empty string. - # - # If the set was created from a single string, it is not normalized. If - # the set is updated the string will be normalized. - # - # Related: #valid_string, #normalized_string, #to_s - def string; @string ||= normalized_string if valid? end - - # Assigns a new string to #string and resets #elements to match. It - # cannot be set to an empty string—assign +nil+ or use #clear instead. - # The string is validated but not normalized. - # - # Use #add or #merge to add a string to an existing set. - # - # Related: #replace, #clear - def string=(str) - if str.nil? - clear - else - str = String.try_convert(str) or raise ArgumentError, "not a string" - tuples = str_to_tuples str - @tuples, @string = [], -str - tuples_add tuples - end - end - - # Returns the \IMAP +sequence-set+ string representation, or an empty - # string when the set is empty. Note that an empty set is invalid in the - # \IMAP syntax. - # - # Related: #valid_string, #normalized_string, #to_s - def to_s; string || "" end - - # Freezes and returns the set. A frozen SequenceSet is Ractor-safe. - def freeze - return self if frozen? - string - @tuples.each(&:freeze).freeze - super - end - - # :call-seq: self == other -> true or false - # - # Returns true when the other SequenceSet represents the same message - # identifiers. Encoding difference—such as order, overlaps, or - # duplicates—are ignored. - # - # Net::IMAP::SequenceSet["1:3"] == Net::IMAP::SequenceSet["1:3"] - # #=> true - # Net::IMAP::SequenceSet["1,2,3"] == Net::IMAP::SequenceSet["1:3"] - # #=> true - # Net::IMAP::SequenceSet["1,3"] == Net::IMAP::SequenceSet["3,1"] - # #=> true - # Net::IMAP::SequenceSet["9,1:*"] == Net::IMAP::SequenceSet["1:*"] - # #=> true - # - # Related: #eql?, #normalize - def ==(other) - self.class == other.class && - (to_s == other.to_s || tuples == other.tuples) - end - - # :call-seq: eql?(other) -> true or false - # - # Hash equality requires the same encoded #string representation. - # - # Net::IMAP::SequenceSet["1:3"] .eql? Net::IMAP::SequenceSet["1:3"] - # #=> true - # Net::IMAP::SequenceSet["1,2,3"].eql? Net::IMAP::SequenceSet["1:3"] - # #=> false - # Net::IMAP::SequenceSet["1,3"] .eql? Net::IMAP::SequenceSet["3,1"] - # #=> false - # Net::IMAP::SequenceSet["9,1:*"].eql? Net::IMAP::SequenceSet["1:*"] - # #=> false - # - # Related: #==, #normalize - def eql?(other) self.class == other.class && string == other.string end - - # See #eql? - def hash; [self.class, string].hash end - - # :call-seq: self === other -> true | false | nil - # - # Returns whether +other+ is contained within the set. Returns +nil+ if a - # StandardError is raised while converting +other+ to a comparable type. - # - # Related: #cover?, #include?, #include_star? - def ===(other) - cover?(other) - rescue - nil - end - - # :call-seq: cover?(other) -> true | false | nil - # - # Returns whether +other+ is contained within the set. +other+ may be any - # object that would be accepted by ::new. - # - # Related: #===, #include?, #include_star? - def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end - - # Returns +true+ when a given number or range is in +self+, and +false+ - # otherwise. Returns +false+ unless +number+ is an Integer, Range, or - # *. - # - # set = Net::IMAP::SequenceSet["5:10,100,111:115"] - # set.include? 1 #=> false - # set.include? 5..10 #=> true - # set.include? 11..20 #=> false - # set.include? 100 #=> true - # set.include? 6 #=> true, covered by "5:10" - # set.include? 4..9 #=> true, covered by "5:10" - # set.include? "4:9" #=> true, strings are parsed - # set.include? 4..9 #=> false, intersection is not sufficient - # set.include? "*" #=> false, use #limit to re-interpret "*" - # set.include? -1 #=> false, -1 is interpreted as "*" - # - # set = Net::IMAP::SequenceSet["5:10,100,111:*"] - # set.include? :* #=> true - # set.include? "*" #=> true - # set.include? -1 #=> true - # set.include? 200.. #=> true - # set.include? 100.. #=> false - # - # Related: #include_star?, #cover?, #=== - def include?(element) include_tuple? input_to_tuple element end - - alias member? include? - - # Returns +true+ when the set contains *. - def include_star?; @tuples.last&.last == STAR_INT end - - # Returns +true+ if the set and a given object have any common elements, - # +false+ otherwise. - # - # Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true - # Net::IMAP::SequenceSet["5:10"].intersect? "11:33" #=> false - # - # Related: #intersection, #disjoint? - def intersect?(other) - valid? && input_to_tuples(other).any? { intersect_tuple? _1 } - end - alias overlap? intersect? - - # Returns +true+ if the set and a given object have no common elements, - # +false+ otherwise. - # - # Net::IMAP::SequenceSet["5:10"].disjoint? "7,9,11" #=> false - # Net::IMAP::SequenceSet["5:10"].disjoint? "11:33" #=> true - # - # Related: #intersection, #intersect? - def disjoint?(other) - empty? || input_to_tuples(other).none? { intersect_tuple? _1 } - end - - # :call-seq: max(star: :*) => integer or star or nil - # - # Returns the maximum value in +self+, +star+ when the set includes - # *, or +nil+ when the set is empty. - def max(star: :*) - (val = @tuples.last&.last) && val == STAR_INT ? star : val - end - - # :call-seq: min(star: :*) => integer or star or nil - # - # Returns the minimum value in +self+, +star+ when the only value in the - # set is *, or +nil+ when the set is empty. - def min(star: :*) - (val = @tuples.first&.first) && val == STAR_INT ? star : val - end - - # :call-seq: minmax(star: :*) => nil or [integer, integer or star] - # - # Returns a 2-element array containing the minimum and maximum numbers in - # +self+, or +nil+ when the set is empty. - def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end - - # Returns false when the set is empty. - def valid?; !empty? end - - # Returns true if the set contains no elements - def empty?; @tuples.empty? end - - # Returns true if the set contains every possible element. - def full?; @tuples == [[1, STAR_INT]] end - - # :call-seq: - # self + other -> sequence set - # self | other -> sequence set - # union(other) -> sequence set - # - # Returns a new sequence set that has every number in the +other+ object - # added. - # - # +other+ may be any object that would be accepted by ::new: a non-zero 32 - # bit unsigned integer, range, sequence-set formatted string, - # another sequence set, or an enumerable containing any of these. - # - # Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99] - # #=> Net::IMAP::SequenceSet["1:6,99"] - # - # Related: #add, #merge - def |(other) remain_frozen dup.merge other end - alias :+ :| - alias union :| - - # :call-seq: - # self - other -> sequence set - # difference(other) -> sequence set - # - # Returns a new sequence set built by duplicating this set and removing - # every number that appears in +other+. - # - # +other+ may be any object that would be accepted by ::new: a non-zero 32 - # bit unsigned integer, range, sequence-set formatted string, - # another sequence set, or an enumerable containing any of these. - # - # Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6 - # #=> Net::IMAP::SequenceSet["1,3,5"] - # - # Related: #subtract - def -(other) remain_frozen dup.subtract other end - alias difference :- - - # :call-seq: - # self & other -> sequence set - # intersection(other) -> sequence set - # - # Returns a new sequence set containing only the numbers common to this - # set and +other+. - # - # +other+ may be any object that would be accepted by ::new: a non-zero 32 - # bit unsigned integer, range, sequence-set formatted string, - # another sequence set, or an enumerable containing any of these. - # - # Net::IMAP::SequenceSet[1..5] & [2, 4, 6] - # #=> Net::IMAP::SequenceSet["2,4"] - # - # (seqset & other) is equivalent to (seqset - ~other). - def &(other) - remain_frozen dup.subtract SequenceSet.new(other).complement! - end - alias intersection :& - - # :call-seq: - # self ^ other -> sequence set - # xor(other) -> sequence set - # - # Returns a new sequence set containing numbers that are exclusive between - # this set and +other+. - # - # +other+ may be any object that would be accepted by ::new: a non-zero 32 - # bit unsigned integer, range, sequence-set formatted string, - # another sequence set, or an enumerable containing any of these. - # - # Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6] - # #=> Net::IMAP::SequenceSet["1,3,5:6"] - # - # (seqset ^ other) is equivalent to ((seqset | other) - - # (seqset & other)). - def ^(other) remain_frozen (self | other).subtract(self & other) end - alias xor :^ - - # :call-seq: - # ~ self -> sequence set - # complement -> sequence set - # - # Returns the complement of self, a SequenceSet which contains all numbers - # _except_ for those in this set. - # - # ~Net::IMAP::SequenceSet.full #=> Net::IMAP::SequenceSet.empty - # ~Net::IMAP::SequenceSet.empty #=> Net::IMAP::SequenceSet.full - # ~Net::IMAP::SequenceSet["1:5,100:222"] - # #=> Net::IMAP::SequenceSet["6:99,223:*"] - # ~Net::IMAP::SequenceSet["6:99,223:*"] - # #=> Net::IMAP::SequenceSet["1:5,100:222"] - # - # Related: #complement! - def ~; remain_frozen dup.complement! end - alias complement :~ - - # :call-seq: - # add(object) -> self - # self << other -> self - # - # Adds a range or number to the set and returns +self+. - # - # #string will be regenerated. Use #merge to add many elements at once. - # - # Related: #add?, #merge, #union - def add(object) - tuple_add input_to_tuple object - normalize! - end - alias << add - - # Adds a range or number to the set and returns +self+. - # - # Unlike #add, #merge, or #union, the new value is appended to #string. - # This may result in a #string which has duplicates or is out-of-order. - def append(object) - tuple = input_to_tuple object - entry = tuple_to_str tuple - tuple_add tuple - @string = -(string ? "#{@string},#{entry}" : entry) - self - end - - # :call-seq: add?(object) -> self or nil - # - # Adds a range or number to the set and returns +self+. Returns +nil+ - # when the object is already included in the set. - # - # #string will be regenerated. Use #merge to add many elements at once. - # - # Related: #add, #merge, #union, #include? - def add?(object) - add object unless include? object - end - - # :call-seq: delete(object) -> self - # - # Deletes the given range or number from the set and returns +self+. - # - # #string will be regenerated after deletion. Use #subtract to remove - # many elements at once. - # - # Related: #delete?, #delete_at, #subtract, #difference - def delete(object) - tuple_subtract input_to_tuple object - normalize! - end - - # :call-seq: - # delete?(number) -> integer or nil - # delete?(star) -> :* or nil - # delete?(range) -> sequence set or nil - # - # Removes a specified value from the set, and returns the removed value. - # Returns +nil+ if nothing was removed. - # - # Returns an integer when the specified +number+ argument was removed: - # set = Net::IMAP::SequenceSet.new [5..10, 20] - # set.delete?(7) #=> 7 - # set #=> # - # set.delete?("20") #=> 20 - # set #=> # - # set.delete?(30) #=> nil - # - # Returns :* when * or -1 is specified and - # removed: - # set = Net::IMAP::SequenceSet.new "5:9,20,35,*" - # set.delete?(-1) #=> :* - # set #=> # - # - # And returns a new SequenceSet when a range is specified: - # - # set = Net::IMAP::SequenceSet.new [5..10, 20] - # set.delete?(9..) #=> # - # set #=> # - # set.delete?(21..) #=> nil - # - # #string will be regenerated after deletion. - # - # Related: #delete, #delete_at, #subtract, #difference, #disjoint? - def delete?(object) - tuple = input_to_tuple object - if tuple.first == tuple.last - return unless include_tuple? tuple - tuple_subtract tuple - normalize! - from_tuple_int tuple.first - else - copy = dup - tuple_subtract tuple - normalize! - copy if copy.subtract(self).valid? - end - end - - # :call-seq: delete_at(index) -> number or :* or nil - # - # Deletes a number the set, indicated by the given +index+. Returns the - # number that was removed, or +nil+ if nothing was removed. - # - # #string will be regenerated after deletion. - # - # Related: #delete, #delete?, #slice!, #subtract, #difference - def delete_at(index) - slice! Integer(index.to_int) - end - - # :call-seq: - # slice!(index) -> integer or :* or nil - # slice!(start, length) -> sequence set or nil - # slice!(range) -> sequence set or nil - # - # Deletes a number or consecutive numbers from the set, indicated by the - # given +index+, +start+ and +length+, or +range+ of offsets. Returns the - # number or sequence set that was removed, or +nil+ if nothing was - # removed. Arguments are interpreted the same as for #slice or #[]. - # - # #string will be regenerated after deletion. - # - # Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference - def slice!(index, length = nil) - deleted = slice(index, length) and subtract deleted - deleted - end - - # Merges all of the elements that appear in any of the +inputs+ into the - # set, and returns +self+. - # - # The +inputs+ may be any objects that would be accepted by ::new: - # non-zero 32 bit unsigned integers, ranges, sequence-set - # formatted strings, other sequence sets, or enumerables containing any of - # these. - # - # #string will be regenerated after all inputs have been merged. - # - # Related: #add, #add?, #union - def merge(*inputs) - tuples_add input_to_tuples inputs - normalize! - end - - # Removes all of the elements that appear in any of the given +objects+ - # from the set, and returns +self+. - # - # The +objects+ may be any objects that would be accepted by ::new: - # non-zero 32 bit unsigned integers, ranges, sequence-set - # formatted strings, other sequence sets, or enumerables containing any of - # these. - # - # Related: #difference - def subtract(*objects) - tuples_subtract input_to_tuples objects - normalize! - end - - # Returns an array of ranges and integers and :*. - # - # The entries are in the same order they appear in #string, with no - # sorting, deduplication, or coalescing. When #string is in its - # normalized form, this will return the same result as #elements. - # This is useful when the given order is significant, for example in a - # ESEARCH response to IMAP#sort. - # - # Related: #each_entry, #elements - def entries; each_entry.to_a end - - # Returns an array of ranges and integers and :*. - # - # The returned elements are sorted and coalesced, even when the input - # #string is not. * will sort last. See #normalize. - # - # By itself, * translates to :*. A range containing - # * translates to an endless range. Use #limit to translate both - # cases to a maximum value. - # - # If the original input was unordered or contains overlapping ranges, the - # returned ranges will be ordered and coalesced. - # - # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements - # #=> [2, 5..9, 11..12, :*] - # - # Related: #each_element, #ranges, #numbers - def elements; each_element.to_a end - alias to_a elements - - # Returns an array of ranges - # - # The returned elements are sorted and coalesced, even when the input - # #string is not. * will sort last. See #normalize. - # - # * translates to an endless range. By itself, * - # translates to :*... Use #limit to set * to a maximum - # value. - # - # The returned ranges will be ordered and coalesced, even when the input - # #string is not. * will sort last. See #normalize. - # - # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges - # #=> [2..2, 5..9, 11..12, :*..] - # Net::IMAP::SequenceSet["123,999:*,456:789"].ranges - # #=> [123..123, 456..789, 999..] - # - # Related: #each_range, #elements, #numbers, #to_set - def ranges; each_range.to_a end - - # Returns a sorted array of all of the number values in the sequence set. - # - # The returned numbers are sorted and de-duplicated, even when the input - # #string is not. See #normalize. - # - # Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers - # #=> [2, 5, 6, 7, 8, 9, 11, 12] - # - # If the set contains a *, RangeError is raised. See #limit. - # - # Net::IMAP::SequenceSet["10000:*"].numbers - # #!> RangeError - # - # *WARNING:* Even excluding sets with *, an enormous result can - # easily be created. An array with over 4 billion integers could be - # returned, requiring up to 32GiB of memory on a 64-bit architecture. - # - # Net::IMAP::SequenceSet[10000..2**32-1].numbers - # # ...probably freezes the process for a while... - # #!> NoMemoryError (probably) - # - # For safety, consider using #limit or #intersection to set an upper - # bound. Alternatively, use #each_element, #each_range, or even - # #each_number to avoid allocation of a result array. - # - # Related: #elements, #ranges, #to_set - def numbers; each_number.to_a end - - # Yields each number or range in #string to the block and returns +self+. - # Returns an enumerator when called without a block. - # - # The entries are yielded in the same order they appear in #tring, with no - # sorting, deduplication, or coalescing. When #string is in its - # normalized form, this will yield the same values as #each_element. - # - # Related: #entries, #each_element - def each_entry(&block) - return to_enum(__method__) unless block_given? - return each_element(&block) unless @string - @string.split(",").each do yield tuple_to_entry str_to_tuple _1 end - self - end - - # Yields each number or range (or :*) in #elements to the block - # and returns self. Returns an enumerator when called without a block. - # - # The returned numbers are sorted and de-duplicated, even when the input - # #string is not. See #normalize. - # - # Related: #elements, #each_entry - def each_element # :yields: integer or range or :* - return to_enum(__method__) unless block_given? - @tuples.each do yield tuple_to_entry _1 end - self - end - - private def tuple_to_entry((min, max)) - if min == STAR_INT then :* - elsif max == STAR_INT then min.. - elsif min == max then min - else min..max - end - end - - # Yields each range in #ranges to the block and returns self. - # Returns an enumerator when called without a block. - # - # Related: #ranges - def each_range # :yields: range - return to_enum(__method__) unless block_given? - @tuples.each do |min, max| - if min == STAR_INT then yield :*.. - elsif max == STAR_INT then yield min.. - else yield min..max - end - end - self - end - - # Yields each number in #numbers to the block and returns self. - # If the set contains a *, RangeError will be raised. - # - # Returns an enumerator when called without a block (even if the set - # contains *). - # - # Related: #numbers - def each_number(&block) # :yields: integer - return to_enum(__method__) unless block_given? - raise RangeError, '%s contains "*"' % [self.class] if include_star? - each_element do |elem| - case elem - when Range then elem.each(&block) - when Integer then block.(elem) - end - end - self - end - - # Returns a Set with all of the #numbers in the sequence set. - # - # If the set contains a *, RangeError will be raised. - # - # See #numbers for the warning about very large sets. - # - # Related: #elements, #ranges, #numbers - def to_set; Set.new(numbers) end - - # Returns the count of #numbers in the set. - # - # If * and 2**32 - 1 (the maximum 32-bit unsigned - # integer value) are both in the set, they will only be counted once. - def count - @tuples.sum(@tuples.count) { _2 - _1 } + - (include_star? && include?(UINT32_MAX) ? -1 : 0) - end - - alias size count - - # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in - # the set. - # - # Related: #[] - def find_index(number) - number = to_tuple_int number - each_tuple_with_index do |min, max, idx_min| - number < min and return nil - number <= max and return from_tuple_int(idx_min + (number - min)) - end - nil - end - - private def each_tuple_with_index - idx_min = 0 - @tuples.each do |min, max| - yield min, max, idx_min, (idx_max = idx_min + (max - min)) - idx_min = idx_max + 1 - end - idx_min - end - - private def reverse_each_tuple_with_index - idx_max = -1 - @tuples.reverse_each do |min, max| - yield min, max, (idx_min = idx_max - (max - min)), idx_max - idx_max = idx_min - 1 - end - idx_max - end - - # :call-seq: at(index) -> integer or nil - # - # Returns a number from +self+, without modifying the set. Behaves the - # same as #[], except that #at only allows a single integer argument. - # - # Related: #[], #slice - def at(index) - index = Integer(index.to_int) - if index.negative? - reverse_each_tuple_with_index do |min, max, idx_min, idx_max| - idx_min <= index and return from_tuple_int(min + (index - idx_min)) - end - else - each_tuple_with_index do |min, _, idx_min, idx_max| - index <= idx_max and return from_tuple_int(min + (index - idx_min)) - end - end - nil - end - - # :call-seq: - # seqset[index] -> integer or :* or nil - # slice(index) -> integer or :* or nil - # seqset[start, length] -> sequence set or nil - # slice(start, length) -> sequence set or nil - # seqset[range] -> sequence set or nil - # slice(range) -> sequence set or nil - # - # Returns a number or a subset from +self+, without modifying the set. - # - # When an Integer argument +index+ is given, the number at offset +index+ - # is returned: - # - # set = Net::IMAP::SequenceSet["10:15,20:23,26"] - # set[0] #=> 10 - # set[5] #=> 15 - # set[10] #=> 26 - # - # If +index+ is negative, it counts relative to the end of +self+: - # set = Net::IMAP::SequenceSet["10:15,20:23,26"] - # set[-1] #=> 26 - # set[-3] #=> 22 - # set[-6] #=> 15 - # - # If +index+ is out of range, +nil+ is returned. - # - # set = Net::IMAP::SequenceSet["10:15,20:23,26"] - # set[11] #=> nil - # set[-12] #=> nil - # - # The result is based on the normalized set—sorted and de-duplicated—not - # on the assigned value of #string. - # - # set = Net::IMAP::SequenceSet["12,20:23,11:16,21"] - # set[0] #=> 11 - # set[-1] #=> 23 - # - def [](index, length = nil) - if length then slice_length(index, length) - elsif index.is_a?(Range) then slice_range(index) - else at(index) - end - end - - alias slice :[] - - private def slice_length(start, length) - start = Integer(start.to_int) - length = Integer(length.to_int) - raise ArgumentError, "length must be positive" unless length.positive? - last = start + length - 1 unless start.negative? && start.abs <= length - slice_range(start..last) - end - - private def slice_range(range) - first = range.begin || 0 - last = range.end || -1 - last -= 1 if range.exclude_end? && range.end && last != STAR_INT - if (first * last).positive? && last < first - SequenceSet.empty - elsif (min = at(first)) - max = at(last) - if max == :* then self & (min..) - elsif min <= max then self & (min..max) - else SequenceSet.empty - end - end - end - - # Returns a frozen SequenceSet with * converted to +max+, numbers - # and ranges over +max+ removed, and ranges containing +max+ converted to - # end at +max+. - # - # Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 20).to_s - # #=> "5,10:20" - # - # * is always interpreted as the maximum value. When the set - # contains *, it will be set equal to the limit. - # - # Net::IMAP::SequenceSet["*"].limit(max: 37) - # #=> Net::IMAP::SequenceSet["37"] - # Net::IMAP::SequenceSet["5:*"].limit(max: 37) - # #=> Net::IMAP::SequenceSet["5:37"] - # Net::IMAP::SequenceSet["500:*"].limit(max: 37) - # #=> Net::IMAP::SequenceSet["37"] - # - def limit(max:) - max = to_tuple_int(max) - if empty? then self.class.empty - elsif !include_star? && max < min then self.class.empty - elsif max(star: STAR_INT) <= max then frozen? ? self : dup.freeze - else dup.limit!(max: max).freeze - end - end - - # Removes all members over +max+ and returns self. If * is a - # member, it will be converted to +max+. - # - # Related: #limit - def limit!(max:) - star = include_star? - max = to_tuple_int(max) - tuple_subtract [max + 1, STAR_INT] - tuple_add [max, max ] if star - normalize! - end - - # :call-seq: complement! -> self - # - # Converts the SequenceSet to its own #complement. It will contain all - # possible values _except_ for those currently in the set. - # - # Related: #complement - def complement! - return replace(self.class.full) if empty? - return clear if full? - flat = @tuples.flat_map { [_1 - 1, _2 + 1] } - if flat.first < 1 then flat.shift else flat.unshift 1 end - if STAR_INT < flat.last then flat.pop else flat.push STAR_INT end - @tuples = flat.each_slice(2).to_a - normalize! - end - - # Returns a new SequenceSet with a normalized string representation. - # - # The returned set's #string is sorted and deduplicated. Adjacent or - # overlapping elements will be merged into a single larger range. - # - # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize - # #=> Net::IMAP::SequenceSet["1:7,9:11"] - # - # Related: #normalize!, #normalized_string - def normalize - str = normalized_string - return self if frozen? && str == string - remain_frozen dup.instance_exec { @string = str&.-@; self } - end - - # Resets #string to be sorted, deduplicated, and coalesced. Returns - # +self+. - # - # Related: #normalize, #normalized_string - def normalize! - @string = nil - self - end - - # Returns a normalized +sequence-set+ string representation, sorted - # and deduplicated. Adjacent or overlapping elements will be merged into - # a single larger range. Returns +nil+ when the set is empty. - # - # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string - # #=> "1:7,9:11" - # - # Related: #normalize!, #normalize - def normalized_string - @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",") - end - - def inspect - if empty? - (frozen? ? "%s.empty" : "#<%s empty>") % [self.class] - elsif frozen? - "%s[%p]" % [self.class, to_s] - else - "#<%s %p>" % [self.class, to_s] - end - end - - # Returns self - alias to_sequence_set itself - - # Unstable API: currently for internal use only (Net::IMAP#validate_data) - def validate # :nodoc: - empty? and raise DataFormatError, "empty sequence-set is invalid" - self - end - - # Unstable API: for internal use only (Net::IMAP#send_data) - def send_data(imap, tag) # :nodoc: - imap.__send__(:put_string, valid_string) - end - - protected - - attr_reader :tuples # :nodoc: - - private - - def remain_frozen(set) frozen? ? set.freeze : set end - - # frozen clones are shallow copied - def initialize_clone(other) - other.frozen? ? super : initialize_dup(other) - end - - def initialize_dup(other) - @tuples = other.tuples.map(&:dup) - @string = other.string&.-@ - super - end - - def input_to_tuple(obj) - obj = input_try_convert obj - case obj - when *STARS, Integer then [int = to_tuple_int(obj), int] - when Range then range_to_tuple(obj) - when String then str_to_tuple(obj) - else - raise DataFormatError, "expected number or range, got %p" % [obj] - end - end - - def input_to_tuples(obj) - obj = input_try_convert obj - case obj - when *STARS, Integer, Range then [input_to_tuple(obj)] - when String then str_to_tuples obj - when SequenceSet then obj.tuples - when ENUMABLE then obj.flat_map { input_to_tuples _1 } - when nil then [] - else - raise DataFormatError, - "expected nz-number, range, string, or enumerable; " \ - "got %p" % [obj] - end - end - - # unlike SequenceSet#try_convert, this returns an Integer, Range, - # String, Set, Array, or... any type of object. - def input_try_convert(input) - SequenceSet.try_convert(input) || - # Integer.try_convert(input) || # ruby 3.1+ - input.respond_to?(:to_int) && Integer(input.to_int) || - String.try_convert(input) || - input - end - - def range_to_tuple(range) - first = to_tuple_int(range.begin || 1) - last = to_tuple_int(range.end || :*) - last -= 1 if range.exclude_end? && range.end && last != STAR_INT - unless first <= last - raise DataFormatError, "invalid range for sequence-set: %p" % [range] - end - [first, last] - end - - def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end - def from_tuple_int(num) num == STAR_INT ? :* : num end - - def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end - def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end - def str_to_tuple(str) - raise DataFormatError, "invalid sequence set string" if str.empty? - str.split(":", 2).map! { to_tuple_int _1 }.minmax - end - - def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end - - def intersect_tuple?((min, max)) - range = range_gte_to(min) and - range.include?(min) || range.include?(max) || (min..max).cover?(range) - end - - def tuples_add(tuples) tuples.each do tuple_add _1 end; self end - def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end - - # - # --|=====| |=====new tuple=====| append - # ?????????-|=====new tuple=====|-|===lower===|-- insert - # - # |=====new tuple=====| - # ---------??=======lower=======??--------------- noop - # - # ---------??===lower==|--|==| join remaining - # ---------??===lower==|--|==|----|===upper===|-- join until upper - # ---------??===lower==|--|==|--|=====upper===|-- join to upper - def tuple_add(tuple) - min, max = tuple - lower, lower_idx = tuple_gte_with_index(min - 1) - if lower.nil? then tuples << tuple - elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple) - else tuple_coalesce(lower, lower_idx, min, max) - end - end - - def tuple_coalesce(lower, lower_idx, min, max) - return if lower.first <= min && max <= lower.last - lower[0] = [min, lower.first].min - lower[1] = [max, lower.last].max - lower_idx += 1 - return if lower_idx == tuples.count - tmax_adj = lower.last + 1 - upper, upper_idx = tuple_gte_with_index(tmax_adj) - if upper - tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last) - end - tuples.slice!(lower_idx..upper_idx) - end - - # |====tuple================| - # --|====| no more 1. noop - # --|====|---------------------------|====lower====|-- 2. noop - # -------|======lower================|---------------- 3. split - # --------|=====lower================|---------------- 4. trim beginning - # - # -------|======lower====????????????----------------- trim lower - # --------|=====lower====????????????----------------- delete lower - # - # -------??=====lower===============|----------------- 5. trim/delete one - # -------??=====lower====|--|====| no more 6. delete rest - # -------??=====lower====|--|====|---|====upper====|-- 7. delete until - # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim - def tuple_subtract(tuple) - min, max = tuple - lower, idx = tuple_gte_with_index(min) - if lower.nil? then nil # case 1. - elsif max < lower.first then nil # case 2. - elsif max < lower.last then tuple_trim_or_split lower, idx, min, max - else tuples_trim_or_delete lower, idx, min, max - end - end - - def tuple_trim_or_split(lower, idx, tmin, tmax) - if lower.first < tmin # split - tuples.insert(idx, [lower.first, tmin - 1]) - end - lower[0] = tmax + 1 - end - - def tuples_trim_or_delete(lower, lower_idx, tmin, tmax) - if lower.first < tmin # trim lower - lower[1] = tmin - 1 - lower_idx += 1 - end - if tmax == lower.last # case 5 - upper_idx = lower_idx - elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1)) - upper_idx -= 1 # cases 7 and 8 - upper[0] = tmax + 1 if upper.first <= tmax # case 8 (else case 7) - end - tuples.slice!(lower_idx..upper_idx) - end - - def tuple_gte_with_index(num) - idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx] - end - - def range_gte_to(num) - first, last = tuples.bsearch { _2 >= num } - first..last if first - end - - def nz_number(num) - case num - when Integer, /\A[1-9]\d*\z/ then num = Integer(num) - else raise DataFormatError, "%p is not a valid nz-number" % [num] - end - NumValidator.ensure_nz_number(num) - num - end - - # intentionally defined after the class implementation - - EMPTY = new.freeze - FULL = self["1:*"] - private_constant :EMPTY, :FULL - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/nameprep.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/nameprep.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/nameprep.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/nameprep.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - module StringPrep - - # Defined in RFC3491[https://tools.ietf.org/html/rfc3491], the +nameprep+ - # profile of "Stringprep" is: - # >>> - # used by the IDNA protocol for preparing domain names; it is not - # designed for any other purpose. It is explicitly not designed for - # processing arbitrary free text and SHOULD NOT be used for that - # purpose. - # - # ... - # - # This profile specifies prohibiting using the following tables...: - # - # - C.1.2 (Non-ASCII space characters) - # - C.2.2 (Non-ASCII control characters) - # - C.3 (Private use characters) - # - C.4 (Non-character code points) - # - C.5 (Surrogate codes) - # - C.6 (Inappropriate for plain text) - # - C.7 (Inappropriate for canonical representation) - # - C.8 (Change display properties are deprecated) - # - C.9 (Tagging characters) - # - # IMPORTANT NOTE: This profile MUST be used with the IDNA protocol. - # The IDNA protocol has additional prohibitions that are checked - # outside of this profile. - module NamePrep - - # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §10 - STRINGPREP_PROFILE = "nameprep" - - # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §2 - UNASSIGNED_TABLE = "A.1" - - # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §3 - MAPPING_TABLES = %w[B.1 B.2].freeze - - # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §4 - NORMALIZATION = :nfkc - - # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §5 - PROHIBITED_TABLES = %w[C.1.2 C.2.2 C.3 C.4 C.5 C.6 C.7 C.8 C.9].freeze - - # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §6 - CHECK_BIDI = true - - module_function - - def nameprep(string, **opts) - StringPrep.stringprep( - string, - unassigned: UNASSIGNED_TABLE, - maps: MAPPING_TABLES, - prohibited: PROHIBITED_TABLES, - normalization: NORMALIZATION, - bidi: CHECK_BIDI, - profile: STRINGPREP_PROFILE, - **opts, - ) - end - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/saslprep.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/saslprep.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/saslprep.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/saslprep.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - module StringPrep - - # SASLprep#saslprep can be used to prepare a string according to [RFC4013]. - # - # \SASLprep maps characters three ways: to nothing, to space, and Unicode - # normalization form KC. \SASLprep prohibits codepoints from nearly all - # standard StringPrep tables (RFC3454, Appendix "C"), and uses - # \StringPrep's standard bidirectional characters requirements (Appendix - # "D"). \SASLprep also uses \StringPrep's definition of "Unassigned" - # codepoints (Appendix "A"). - module SASLprep - - # Used to short-circuit strings that don't need preparation. - ASCII_NO_CTRLS = /\A[\x20-\x7e]*\z/u.freeze - - # Avoid loading these tables unless they are needed (they are only - # needed for non-ASCII). - saslprep_tables = File.expand_path("saslprep_tables", __dir__) - autoload :MAP_TO_NOTHING, saslprep_tables - autoload :MAP_TO_SPACE, saslprep_tables - autoload :PROHIBITED, saslprep_tables - autoload :PROHIBITED_STORED, saslprep_tables - autoload :TABLES_PROHIBITED, saslprep_tables - autoload :TABLES_PROHIBITED_STORED, saslprep_tables - - module_function - - # Prepares a UTF-8 +string+ for comparison, using the \SASLprep profile - # RFC4013 of the StringPrep algorithm RFC3454. - # - # By default, prohibited strings will return +nil+. When +exception+ is - # +true+, a StringPrepError describing the violation will be raised. - # - # When +stored+ is +true+, "unassigned" codepoints will be prohibited. - # For \StringPrep and the \SASLprep profile, "unassigned" refers to - # Unicode 3.2, and not later versions. See RFC3454 §7 for more - # information. - def saslprep(str, stored: false, exception: false) - return str if ASCII_NO_CTRLS.match?(str) # incompatible encoding raises - str = str.encode("UTF-8") # also dups (and raises for invalid encoding) - str.gsub!(MAP_TO_SPACE, " ") - str.gsub!(MAP_TO_NOTHING, "") - str.unicode_normalize!(:nfkc) - # These regexps combine the prohibited and bidirectional checks - return str unless str.match?(stored ? PROHIBITED_STORED : PROHIBITED) - return nil unless exception - # raise helpful errors to indicate *why* it failed: - tables = stored ? TABLES_PROHIBITED_STORED : TABLES_PROHIBITED - StringPrep.check_prohibited! str, *tables, bidi: true, profile: "SASLprep" - raise InvalidStringError.new( - "unknown error", string: string, profile: "SASLprep" - ) - rescue ArgumentError, Encoding::CompatibilityError => ex - if /invalid byte sequence|incompatible encoding/.match? ex.message - return nil unless exception - raise StringPrepError.new(ex.message, string: str, profile: "saslprep") - end - raise ex - end - - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/saslprep_tables.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/saslprep_tables.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/saslprep_tables.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/saslprep_tables.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,96 +0,0 @@ -# frozen_string_literal: true - -#-- -# This file is generated from RFC3454, by rake. Don't edit directly. -#++ - -module Net::IMAP::StringPrep - - module SASLprep - - # RFC4013 §2.1 Mapping - mapped to space - # >>> - # non-ASCII space characters (\StringPrep\[\"C.1.2\"]) that can - # be mapped to SPACE (U+0020) - # - # Equal to \StringPrep\[\"C.1.2\"]. - # Redefined here to avoid loading StringPrep::Tables unless necessary. - MAP_TO_SPACE = /[\u200b\p{Zs}&&[^ ]]/.freeze - - # RFC4013 §2.1 Mapping - mapped to nothing - # >>> - # the "commonly mapped to nothing" characters - # (\StringPrep\[\"B.1\"]) that can be mapped to nothing. - # - # Equal to \StringPrep\[\"B.1\"]. - # Redefined here to avoid loading StringPrep::Tables unless necessary. - MAP_TO_NOTHING = /[\u{00ad 034f 1806 2060 feff}\u{180b}-\u{180d}\u{200b}-\u{200d}\u{fe00}-\u{fe0f}]/.freeze - - # RFC4013 §2.3 Prohibited Output - # >>> - # * Non-ASCII space characters — \StringPrep\[\"C.1.2\"] - # * ASCII control characters — \StringPrep\[\"C.2.1\"] - # * Non-ASCII control characters — \StringPrep\[\"C.2.2\"] - # * Private Use characters — \StringPrep\[\"C.3\"] - # * Non-character code points — \StringPrep\[\"C.4\"] - # * Surrogate code points — \StringPrep\[\"C.5\"] - # * Inappropriate for plain text characters — \StringPrep\[\"C.6\"] - # * Inappropriate for canonical representation characters — \StringPrep\[\"C.7\"] - # * Change display properties or deprecated characters — \StringPrep\[\"C.8\"] - # * Tagging characters — \StringPrep\[\"C.9\"] - TABLES_PROHIBITED = ["C.1.2", "C.2.1", "C.2.2", "C.3", "C.4", "C.5", "C.6", "C.7", "C.8", "C.9"].freeze - - # Adds unassigned (by Unicode 3.2) codepoints to TABLES_PROHIBITED. - # - # RFC4013 §2.5 Unassigned Code Points - # >>> - # This profile specifies the \StringPrep\[\"A.1\"] table as its - # list of unassigned code points. - TABLES_PROHIBITED_STORED = ["A.1", *TABLES_PROHIBITED].freeze - - # A Regexp matching codepoints prohibited by RFC4013 §2.3. - # - # This combines all of the TABLES_PROHIBITED tables. - PROHIBITED_OUTPUT = /[\u{06dd 070f 1680 180e 3000 feff e0001}\u{0000}-\u{001f}\u{007f}-\u{00a0}\u{0340}-\u{0341}\u{2000}-\u{200f}\u{2028}-\u{202f}\u{205f}-\u{2063}\u{206a}-\u{206f}\u{2ff0}-\u{2ffb}\u{e000}-\u{f8ff}\u{fdd0}-\u{fdef}\u{fff9}-\u{ffff}\u{1d173}-\u{1d17a}\u{1fffe}-\u{1ffff}\u{2fffe}-\u{2ffff}\u{3fffe}-\u{3ffff}\u{4fffe}-\u{4ffff}\u{5fffe}-\u{5ffff}\u{6fffe}-\u{6ffff}\u{7fffe}-\u{7ffff}\u{8fffe}-\u{8ffff}\u{9fffe}-\u{9ffff}\u{afffe}-\u{affff}\u{bfffe}-\u{bffff}\u{cfffe}-\u{cffff}\u{dfffe}-\u{dffff}\u{e0020}-\u{e007f}\u{efffe}-\u{10ffff}\p{Cs}]/.freeze - - # RFC4013 §2.5 Unassigned Code Points - # >>> - # This profile specifies the \StringPrep\[\"A.1\"] table as its - # list of unassigned code points. - # - # Equal to \StringPrep\[\"A.1\"]. - # Redefined here to avoid loading StringPrep::Tables unless necessary. - UNASSIGNED = /\p{^AGE=3.2}/.freeze - - # A Regexp matching codepoints prohibited by RFC4013 §2.3 and §2.5. - # - # This combines PROHIBITED_OUTPUT and UNASSIGNED. - PROHIBITED_OUTPUT_STORED = Regexp.union( - UNASSIGNED, PROHIBITED_OUTPUT - ).freeze - - # Bidirectional Characters [StringPrep, §6] - # - # A Regexp for strings that don't satisfy StringPrep's Bidirectional - # Characters rules. - # - # Equal to StringPrep::Tables::BIDI_FAILURE. - # Redefined here to avoid loading StringPrep::Tables unless necessary. - BIDI_FAILURE = /(?-mix:(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]))|(?m-ix:(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])))|(?-mix:(?m-ix:\A(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))|(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])\z))/.freeze - - # A Regexp matching strings prohibited by RFC4013 §2.3 and §2.4. - # - # This combines PROHIBITED_OUTPUT and BIDI_FAILURE. - PROHIBITED = Regexp.union( - PROHIBITED_OUTPUT, BIDI_FAILURE, - ) - - # A Regexp matching strings prohibited by RFC4013 §2.3, §2.4, and §2.5. - # - # This combines PROHIBITED_OUTPUT_STORED and BIDI_FAILURE. - PROHIBITED_STORED = Regexp.union( - PROHIBITED_OUTPUT_STORED, BIDI_FAILURE, - ) - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/tables.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/tables.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/tables.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/tables.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,146 +0,0 @@ -# frozen_string_literal: true - -#-- -# This file is generated from RFC3454, by rake. Don't edit directly. -#++ - -module Net::IMAP::StringPrep - - module Tables - - # Unassigned code points in Unicode 3.2 \StringPrep\[\"A.1\"] - IN_A_1 = /\p{^AGE=3.2}/.freeze - - # Commonly mapped to nothing \StringPrep\[\"B.1\"] - IN_B_1 = /[\u{00ad 034f 1806 2060 feff}\u{180b}-\u{180d}\u{200b}-\u{200d}\u{fe00}-\u{fe0f}]/.freeze - - # Mapping for case-folding used with NFKC \StringPrep\[\"B.2\"] - IN_B_2 = /[\u{00b5 0100 0102 0104 0106 0108 010a 010c 010e 0110 0112 0114 0116 0118 011a 011c 011e 0120 0122 0124 0126 0128 012a 012c 012e 0130 0132 0134 0136 0139 013b 013d 013f 0141 0143 0145 0147 014c 014e 0150 0152 0154 0156 0158 015a 015c 015e 0160 0162 0164 0166 0168 016a 016c 016e 0170 0172 0174 0176 017b 017d 017f 0184 01a2 01a4 01a9 01ac 01b5 01bc 01cd 01cf 01d1 01d3 01d5 01d7 01d9 01db 01de 01e0 01e2 01e4 01e6 01e8 01ea 01ec 01ee 01f4 01fa 01fc 01fe 0200 0202 0204 0206 0208 020a 020c 020e 0210 0212 0214 0216 0218 021a 021c 021e 0220 0222 0224 0226 0228 022a 022c 022e 0230 0232 0345 037a 0386 038c 03b0 03c2 03d8 03da 03dc 03de 03e0 03e2 03e4 03e6 03e8 03ea 03ec 03ee 0460 0462 0464 0466 0468 046a 046c 046e 0470 0472 0474 0476 0478 047a 047c 047e 0480 048a 048c 048e 0490 0492 0494 0496 0498 049a 049c 049e 04a0 04a2 04a4 04a6 04a8 04aa 04ac 04ae 04b0 04b2 04b4 04b6 04b8 04ba 04bc 04be 04c1 04c3 04c5 04c7 04c9 04cb 04cd 04d0 04d2 04d4 04d6 04d8 04da 04dc 04de 04e0 04e2 04e4 04e6 04e8 04ea 04ec 04ee 04f0 04f2 04f4 04f8 0500 0502 0504 0506 0508 050a 050c 050e 0587 1e00 1e02 1e04 1e06 1e08 1e0a 1e0c 1e0e 1e10 1e12 1e14 1e16 1e18 1e1a 1e1c 1e1e 1e20 1e22 1e24 1e26 1e28 1e2a 1e2c 1e2e 1e30 1e32 1e34 1e36 1e38 1e3a 1e3c 1e3e 1e40 1e42 1e44 1e46 1e48 1e4a 1e4c 1e4e 1e50 1e52 1e54 1e56 1e58 1e5a 1e5c 1e5e 1e60 1e62 1e64 1e66 1e68 1e6a 1e6c 1e6e 1e70 1e72 1e74 1e76 1e78 1e7a 1e7c 1e7e 1e80 1e82 1e84 1e86 1e88 1e8a 1e8c 1e8e 1e90 1e92 1e94 1ea0 1ea2 1ea4 1ea6 1ea8 1eaa 1eac 1eae 1eb0 1eb2 1eb4 1eb6 1eb8 1eba 1ebc 1ebe 1ec0 1ec2 1ec4 1ec6 1ec8 1eca 1ecc 1ece 1ed0 1ed2 1ed4 1ed6 1ed8 1eda 1edc 1ede 1ee0 1ee2 1ee4 1ee6 1ee8 1eea 1eec 1eee 1ef0 1ef2 1ef4 1ef6 1ef8 1f50 1f52 1f54 1f56 1f59 1f5b 1f5d 1f5f 1fbe 20a8 2107 2109 2124 2126 2128 2133 2145 3371 3373 3375 33c3 33cb 33d7 1d49c 1d4a2 1d546 1d6d3 1d70d 1d747 1d781 1d7bb}\u{0041}-\u{005a}\u{00c0}-\u{00d6}\u{00d8}-\u{00df}\u{0149}-\u{014a}\u{0178}-\u{0179}\u{0181}-\u{0182}\u{0186}-\u{0187}\u{0189}-\u{018b}\u{018e}-\u{0191}\u{0193}-\u{0194}\u{0196}-\u{0198}\u{019c}-\u{019d}\u{019f}-\u{01a0}\u{01a6}-\u{01a7}\u{01ae}-\u{01af}\u{01b1}-\u{01b3}\u{01b7}-\u{01b8}\u{01c4}-\u{01c5}\u{01c7}-\u{01c8}\u{01ca}-\u{01cb}\u{01f0}-\u{01f2}\u{01f6}-\u{01f8}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ab}\u{03d0}-\u{03d6}\u{03f0}-\u{03f2}\u{03f4}-\u{03f5}\u{0400}-\u{042f}\u{0531}-\u{0556}\u{1e96}-\u{1e9b}\u{1f08}-\u{1f0f}\u{1f18}-\u{1f1d}\u{1f28}-\u{1f2f}\u{1f38}-\u{1f3f}\u{1f48}-\u{1f4d}\u{1f68}-\u{1f6f}\u{1f80}-\u{1faf}\u{1fb2}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd2}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe2}-\u{1fe4}\u{1fe6}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{2102}-\u{2103}\u{210b}-\u{210d}\u{2110}-\u{2112}\u{2115}-\u{2116}\u{2119}-\u{211d}\u{2120}-\u{2122}\u{212a}-\u{212d}\u{2130}-\u{2131}\u{213e}-\u{213f}\u{2160}-\u{216f}\u{24b6}-\u{24cf}\u{3380}-\u{3387}\u{338a}-\u{338c}\u{3390}-\u{3394}\u{33a9}-\u{33ac}\u{33b4}-\u{33c1}\u{33c6}-\u{33c9}\u{33cd}-\u{33ce}\u{33d9}-\u{33da}\u{33dc}-\u{33dd}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{10400}-\u{10425}\u{1d400}-\u{1d419}\u{1d434}-\u{1d44d}\u{1d468}-\u{1d481}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b5}\u{1d4d0}-\u{1d4e9}\u{1d504}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d538}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d56c}-\u{1d585}\u{1d5a0}-\u{1d5b9}\u{1d5d4}-\u{1d5ed}\u{1d608}-\u{1d621}\u{1d63c}-\u{1d655}\u{1d670}-\u{1d689}\u{1d6a8}-\u{1d6c0}\u{1d6e2}-\u{1d6fa}\u{1d71c}-\u{1d734}\u{1d756}-\u{1d76e}\u{1d790}-\u{1d7a8}]/.freeze - - # Mapping for case-folding used with no normalization \StringPrep\[\"B.3\"] - IN_B_3 = /[\u{00b5 0100 0102 0104 0106 0108 010a 010c 010e 0110 0112 0114 0116 0118 011a 011c 011e 0120 0122 0124 0126 0128 012a 012c 012e 0130 0132 0134 0136 0139 013b 013d 013f 0141 0143 0145 0147 014c 014e 0150 0152 0154 0156 0158 015a 015c 015e 0160 0162 0164 0166 0168 016a 016c 016e 0170 0172 0174 0176 017b 017d 017f 0184 01a2 01a4 01a9 01ac 01b5 01bc 01cd 01cf 01d1 01d3 01d5 01d7 01d9 01db 01de 01e0 01e2 01e4 01e6 01e8 01ea 01ec 01ee 01f4 01fa 01fc 01fe 0200 0202 0204 0206 0208 020a 020c 020e 0210 0212 0214 0216 0218 021a 021c 021e 0220 0222 0224 0226 0228 022a 022c 022e 0230 0232 0345 0386 038c 03b0 03c2 03d8 03da 03dc 03de 03e0 03e2 03e4 03e6 03e8 03ea 03ec 03ee 0460 0462 0464 0466 0468 046a 046c 046e 0470 0472 0474 0476 0478 047a 047c 047e 0480 048a 048c 048e 0490 0492 0494 0496 0498 049a 049c 049e 04a0 04a2 04a4 04a6 04a8 04aa 04ac 04ae 04b0 04b2 04b4 04b6 04b8 04ba 04bc 04be 04c1 04c3 04c5 04c7 04c9 04cb 04cd 04d0 04d2 04d4 04d6 04d8 04da 04dc 04de 04e0 04e2 04e4 04e6 04e8 04ea 04ec 04ee 04f0 04f2 04f4 04f8 0500 0502 0504 0506 0508 050a 050c 050e 0587 1e00 1e02 1e04 1e06 1e08 1e0a 1e0c 1e0e 1e10 1e12 1e14 1e16 1e18 1e1a 1e1c 1e1e 1e20 1e22 1e24 1e26 1e28 1e2a 1e2c 1e2e 1e30 1e32 1e34 1e36 1e38 1e3a 1e3c 1e3e 1e40 1e42 1e44 1e46 1e48 1e4a 1e4c 1e4e 1e50 1e52 1e54 1e56 1e58 1e5a 1e5c 1e5e 1e60 1e62 1e64 1e66 1e68 1e6a 1e6c 1e6e 1e70 1e72 1e74 1e76 1e78 1e7a 1e7c 1e7e 1e80 1e82 1e84 1e86 1e88 1e8a 1e8c 1e8e 1e90 1e92 1e94 1ea0 1ea2 1ea4 1ea6 1ea8 1eaa 1eac 1eae 1eb0 1eb2 1eb4 1eb6 1eb8 1eba 1ebc 1ebe 1ec0 1ec2 1ec4 1ec6 1ec8 1eca 1ecc 1ece 1ed0 1ed2 1ed4 1ed6 1ed8 1eda 1edc 1ede 1ee0 1ee2 1ee4 1ee6 1ee8 1eea 1eec 1eee 1ef0 1ef2 1ef4 1ef6 1ef8 1f50 1f52 1f54 1f56 1f59 1f5b 1f5d 1f5f 1fbe 2126}\u{0041}-\u{005a}\u{00c0}-\u{00d6}\u{00d8}-\u{00df}\u{0149}-\u{014a}\u{0178}-\u{0179}\u{0181}-\u{0182}\u{0186}-\u{0187}\u{0189}-\u{018b}\u{018e}-\u{0191}\u{0193}-\u{0194}\u{0196}-\u{0198}\u{019c}-\u{019d}\u{019f}-\u{01a0}\u{01a6}-\u{01a7}\u{01ae}-\u{01af}\u{01b1}-\u{01b3}\u{01b7}-\u{01b8}\u{01c4}-\u{01c5}\u{01c7}-\u{01c8}\u{01ca}-\u{01cb}\u{01f0}-\u{01f2}\u{01f6}-\u{01f8}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ab}\u{03d0}-\u{03d1}\u{03d5}-\u{03d6}\u{03f0}-\u{03f2}\u{03f4}-\u{03f5}\u{0400}-\u{042f}\u{0531}-\u{0556}\u{1e96}-\u{1e9b}\u{1f08}-\u{1f0f}\u{1f18}-\u{1f1d}\u{1f28}-\u{1f2f}\u{1f38}-\u{1f3f}\u{1f48}-\u{1f4d}\u{1f68}-\u{1f6f}\u{1f80}-\u{1faf}\u{1fb2}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd2}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe2}-\u{1fe4}\u{1fe6}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{212a}-\u{212b}\u{2160}-\u{216f}\u{24b6}-\u{24cf}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{10400}-\u{10425}]/.freeze - - # Replacements for IN_B.1 - MAP_B_1 = "".freeze - - # Replacements for IN_B.2 - MAP_B_2 = {"A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e", "F"=>"f", "G"=>"g", "H"=>"h", "I"=>"i", "J"=>"j", "K"=>"k", "L"=>"l", "M"=>"m", "N"=>"n", "O"=>"o", "P"=>"p", "Q"=>"q", "R"=>"r", "S"=>"s", "T"=>"t", "U"=>"u", "V"=>"v", "W"=>"w", "X"=>"x", "Y"=>"y", "Z"=>"z", "µ"=>"μ", "À"=>"à", "Á"=>"á", "Â"=>"â", "Ã"=>"ã", "Ä"=>"ä", "Å"=>"å", "Æ"=>"æ", "Ç"=>"ç", "È"=>"è", "É"=>"é", "Ê"=>"ê", "Ë"=>"ë", "Ì"=>"ì", "Í"=>"í", "Î"=>"î", "Ï"=>"ï", "Ð"=>"ð", "Ñ"=>"ñ", "Ò"=>"ò", "Ó"=>"ó", "Ô"=>"ô", "Õ"=>"õ", "Ö"=>"ö", "Ø"=>"ø", "Ù"=>"ù", "Ú"=>"ú", "Û"=>"û", "Ü"=>"ü", "Ý"=>"ý", "Þ"=>"þ", "ß"=>"ss", "Ā"=>"ā", "Ă"=>"ă", "Ą"=>"ą", "Ć"=>"ć", "Ĉ"=>"ĉ", "Ċ"=>"ċ", "Č"=>"č", "Ď"=>"ď", "Đ"=>"đ", "Ē"=>"ē", "Ĕ"=>"ĕ", "Ė"=>"ė", "Ę"=>"ę", "Ě"=>"ě", "Ĝ"=>"ĝ", "Ğ"=>"ğ", "Ġ"=>"ġ", "Ģ"=>"ģ", "Ĥ"=>"ĥ", "Ħ"=>"ħ", "Ĩ"=>"ĩ", "Ī"=>"ī", "Ĭ"=>"ĭ", "Į"=>"į", "İ"=>"i̇", "IJ"=>"ij", "Ĵ"=>"ĵ", "Ķ"=>"ķ", "Ĺ"=>"ĺ", "Ļ"=>"ļ", "Ľ"=>"ľ", "Ŀ"=>"ŀ", "Ł"=>"ł", "Ń"=>"ń", "Ņ"=>"ņ", "Ň"=>"ň", "ʼn"=>"ʼn", "Ŋ"=>"ŋ", "Ō"=>"ō", "Ŏ"=>"ŏ", "Ő"=>"ő", "Œ"=>"œ", "Ŕ"=>"ŕ", "Ŗ"=>"ŗ", "Ř"=>"ř", "Ś"=>"ś", "Ŝ"=>"ŝ", "Ş"=>"ş", "Š"=>"š", "Ţ"=>"ţ", "Ť"=>"ť", "Ŧ"=>"ŧ", "Ũ"=>"ũ", "Ū"=>"ū", "Ŭ"=>"ŭ", "Ů"=>"ů", "Ű"=>"ű", "Ų"=>"ų", "Ŵ"=>"ŵ", "Ŷ"=>"ŷ", "Ÿ"=>"ÿ", "Ź"=>"ź", "Ż"=>"ż", "Ž"=>"ž", "ſ"=>"s", "Ɓ"=>"ɓ", "Ƃ"=>"ƃ", "Ƅ"=>"ƅ", "Ɔ"=>"ɔ", "Ƈ"=>"ƈ", "Ɖ"=>"ɖ", "Ɗ"=>"ɗ", "Ƌ"=>"ƌ", "Ǝ"=>"ǝ", "Ə"=>"ə", "Ɛ"=>"ɛ", "Ƒ"=>"ƒ", "Ɠ"=>"ɠ", "Ɣ"=>"ɣ", "Ɩ"=>"ɩ", "Ɨ"=>"ɨ", "Ƙ"=>"ƙ", "Ɯ"=>"ɯ", "Ɲ"=>"ɲ", "Ɵ"=>"ɵ", "Ơ"=>"ơ", "Ƣ"=>"ƣ", "Ƥ"=>"ƥ", "Ʀ"=>"ʀ", "Ƨ"=>"ƨ", "Ʃ"=>"ʃ", "Ƭ"=>"ƭ", "Ʈ"=>"ʈ", "Ư"=>"ư", "Ʊ"=>"ʊ", "Ʋ"=>"ʋ", "Ƴ"=>"ƴ", "Ƶ"=>"ƶ", "Ʒ"=>"ʒ", "Ƹ"=>"ƹ", "Ƽ"=>"ƽ", "DŽ"=>"dž", "Dž"=>"dž", "LJ"=>"lj", "Lj"=>"lj", "NJ"=>"nj", "Nj"=>"nj", "Ǎ"=>"ǎ", "Ǐ"=>"ǐ", "Ǒ"=>"ǒ", "Ǔ"=>"ǔ", "Ǖ"=>"ǖ", "Ǘ"=>"ǘ", "Ǚ"=>"ǚ", "Ǜ"=>"ǜ", "Ǟ"=>"ǟ", "Ǡ"=>"ǡ", "Ǣ"=>"ǣ", "Ǥ"=>"ǥ", "Ǧ"=>"ǧ", "Ǩ"=>"ǩ", "Ǫ"=>"ǫ", "Ǭ"=>"ǭ", "Ǯ"=>"ǯ", "ǰ"=>"ǰ", "DZ"=>"dz", "Dz"=>"dz", "Ǵ"=>"ǵ", "Ƕ"=>"ƕ", "Ƿ"=>"ƿ", "Ǹ"=>"ǹ", "Ǻ"=>"ǻ", "Ǽ"=>"ǽ", "Ǿ"=>"ǿ", "Ȁ"=>"ȁ", "Ȃ"=>"ȃ", "Ȅ"=>"ȅ", "Ȇ"=>"ȇ", "Ȉ"=>"ȉ", "Ȋ"=>"ȋ", "Ȍ"=>"ȍ", "Ȏ"=>"ȏ", "Ȑ"=>"ȑ", "Ȓ"=>"ȓ", "Ȕ"=>"ȕ", "Ȗ"=>"ȗ", "Ș"=>"ș", "Ț"=>"ț", "Ȝ"=>"ȝ", "Ȟ"=>"ȟ", "Ƞ"=>"ƞ", "Ȣ"=>"ȣ", "Ȥ"=>"ȥ", "Ȧ"=>"ȧ", "Ȩ"=>"ȩ", "Ȫ"=>"ȫ", "Ȭ"=>"ȭ", "Ȯ"=>"ȯ", "Ȱ"=>"ȱ", "Ȳ"=>"ȳ", "ͅ"=>"ι", "ͺ"=>" ι", "Ά"=>"ά", "Έ"=>"έ", "Ή"=>"ή", "Ί"=>"ί", "Ό"=>"ό", "Ύ"=>"ύ", "Ώ"=>"ώ", "ΐ"=>"ΐ", "Α"=>"α", "Β"=>"β", "Γ"=>"γ", "Δ"=>"δ", "Ε"=>"ε", "Ζ"=>"ζ", "Η"=>"η", "Θ"=>"θ", "Ι"=>"ι", "Κ"=>"κ", "Λ"=>"λ", "Μ"=>"μ", "Ν"=>"ν", "Ξ"=>"ξ", "Ο"=>"ο", "Π"=>"π", "Ρ"=>"ρ", "Σ"=>"σ", "Τ"=>"τ", "Υ"=>"υ", "Φ"=>"φ", "Χ"=>"χ", "Ψ"=>"ψ", "Ω"=>"ω", "Ϊ"=>"ϊ", "Ϋ"=>"ϋ", "ΰ"=>"ΰ", "ς"=>"σ", "ϐ"=>"β", "ϑ"=>"θ", "ϒ"=>"υ", "ϓ"=>"ύ", "ϔ"=>"ϋ", "ϕ"=>"φ", "ϖ"=>"π", "Ϙ"=>"ϙ", "Ϛ"=>"ϛ", "Ϝ"=>"ϝ", "Ϟ"=>"ϟ", "Ϡ"=>"ϡ", "Ϣ"=>"ϣ", "Ϥ"=>"ϥ", "Ϧ"=>"ϧ", "Ϩ"=>"ϩ", "Ϫ"=>"ϫ", "Ϭ"=>"ϭ", "Ϯ"=>"ϯ", "ϰ"=>"κ", "ϱ"=>"ρ", "ϲ"=>"σ", "ϴ"=>"θ", "ϵ"=>"ε", "Ѐ"=>"ѐ", "Ё"=>"ё", "Ђ"=>"ђ", "Ѓ"=>"ѓ", "Є"=>"є", "Ѕ"=>"ѕ", "І"=>"і", "Ї"=>"ї", "Ј"=>"ј", "Љ"=>"љ", "Њ"=>"њ", "Ћ"=>"ћ", "Ќ"=>"ќ", "Ѝ"=>"ѝ", "Ў"=>"ў", "Џ"=>"џ", "А"=>"а", "Б"=>"б", "В"=>"в", "Г"=>"г", "Д"=>"д", "Е"=>"е", "Ж"=>"ж", "З"=>"з", "И"=>"и", "Й"=>"й", "К"=>"к", "Л"=>"л", "М"=>"м", "Н"=>"н", "О"=>"о", "П"=>"п", "Р"=>"р", "С"=>"с", "Т"=>"т", "У"=>"у", "Ф"=>"ф", "Х"=>"х", "Ц"=>"ц", "Ч"=>"ч", "Ш"=>"ш", "Щ"=>"щ", "Ъ"=>"ъ", "Ы"=>"ы", "Ь"=>"ь", "Э"=>"э", "Ю"=>"ю", "Я"=>"я", "Ѡ"=>"ѡ", "Ѣ"=>"ѣ", "Ѥ"=>"ѥ", "Ѧ"=>"ѧ", "Ѩ"=>"ѩ", "Ѫ"=>"ѫ", "Ѭ"=>"ѭ", "Ѯ"=>"ѯ", "Ѱ"=>"ѱ", "Ѳ"=>"ѳ", "Ѵ"=>"ѵ", "Ѷ"=>"ѷ", "Ѹ"=>"ѹ", "Ѻ"=>"ѻ", "Ѽ"=>"ѽ", "Ѿ"=>"ѿ", "Ҁ"=>"ҁ", "Ҋ"=>"ҋ", "Ҍ"=>"ҍ", "Ҏ"=>"ҏ", "Ґ"=>"ґ", "Ғ"=>"ғ", "Ҕ"=>"ҕ", "Җ"=>"җ", "Ҙ"=>"ҙ", "Қ"=>"қ", "Ҝ"=>"ҝ", "Ҟ"=>"ҟ", "Ҡ"=>"ҡ", "Ң"=>"ң", "Ҥ"=>"ҥ", "Ҧ"=>"ҧ", "Ҩ"=>"ҩ", "Ҫ"=>"ҫ", "Ҭ"=>"ҭ", "Ү"=>"ү", "Ұ"=>"ұ", "Ҳ"=>"ҳ", "Ҵ"=>"ҵ", "Ҷ"=>"ҷ", "Ҹ"=>"ҹ", "Һ"=>"һ", "Ҽ"=>"ҽ", "Ҿ"=>"ҿ", "Ӂ"=>"ӂ", "Ӄ"=>"ӄ", "Ӆ"=>"ӆ", "Ӈ"=>"ӈ", "Ӊ"=>"ӊ", "Ӌ"=>"ӌ", "Ӎ"=>"ӎ", "Ӑ"=>"ӑ", "Ӓ"=>"ӓ", "Ӕ"=>"ӕ", "Ӗ"=>"ӗ", "Ә"=>"ә", "Ӛ"=>"ӛ", "Ӝ"=>"ӝ", "Ӟ"=>"ӟ", "Ӡ"=>"ӡ", "Ӣ"=>"ӣ", "Ӥ"=>"ӥ", "Ӧ"=>"ӧ", "Ө"=>"ө", "Ӫ"=>"ӫ", "Ӭ"=>"ӭ", "Ӯ"=>"ӯ", "Ӱ"=>"ӱ", "Ӳ"=>"ӳ", "Ӵ"=>"ӵ", "Ӹ"=>"ӹ", "Ԁ"=>"ԁ", "Ԃ"=>"ԃ", "Ԅ"=>"ԅ", "Ԇ"=>"ԇ", "Ԉ"=>"ԉ", "Ԋ"=>"ԋ", "Ԍ"=>"ԍ", "Ԏ"=>"ԏ", "Ա"=>"ա", "Բ"=>"բ", "Գ"=>"գ", "Դ"=>"դ", "Ե"=>"ե", "Զ"=>"զ", "Է"=>"է", "Ը"=>"ը", "Թ"=>"թ", "Ժ"=>"ժ", "Ի"=>"ի", "Լ"=>"լ", "Խ"=>"խ", "Ծ"=>"ծ", "Կ"=>"կ", "Հ"=>"հ", "Ձ"=>"ձ", "Ղ"=>"ղ", "Ճ"=>"ճ", "Մ"=>"մ", "Յ"=>"յ", "Ն"=>"ն", "Շ"=>"շ", "Ո"=>"ո", "Չ"=>"չ", "Պ"=>"պ", "Ջ"=>"ջ", "Ռ"=>"ռ", "Ս"=>"ս", "Վ"=>"վ", "Տ"=>"տ", "Ր"=>"ր", "Ց"=>"ց", "Ւ"=>"ւ", "Փ"=>"փ", "Ք"=>"ք", "Օ"=>"օ", "Ֆ"=>"ֆ", "և"=>"եւ", "Ḁ"=>"ḁ", "Ḃ"=>"ḃ", "Ḅ"=>"ḅ", "Ḇ"=>"ḇ", "Ḉ"=>"ḉ", "Ḋ"=>"ḋ", "Ḍ"=>"ḍ", "Ḏ"=>"ḏ", "Ḑ"=>"ḑ", "Ḓ"=>"ḓ", "Ḕ"=>"ḕ", "Ḗ"=>"ḗ", "Ḙ"=>"ḙ", "Ḛ"=>"ḛ", "Ḝ"=>"ḝ", "Ḟ"=>"ḟ", "Ḡ"=>"ḡ", "Ḣ"=>"ḣ", "Ḥ"=>"ḥ", "Ḧ"=>"ḧ", "Ḩ"=>"ḩ", "Ḫ"=>"ḫ", "Ḭ"=>"ḭ", "Ḯ"=>"ḯ", "Ḱ"=>"ḱ", "Ḳ"=>"ḳ", "Ḵ"=>"ḵ", "Ḷ"=>"ḷ", "Ḹ"=>"ḹ", "Ḻ"=>"ḻ", "Ḽ"=>"ḽ", "Ḿ"=>"ḿ", "Ṁ"=>"ṁ", "Ṃ"=>"ṃ", "Ṅ"=>"ṅ", "Ṇ"=>"ṇ", "Ṉ"=>"ṉ", "Ṋ"=>"ṋ", "Ṍ"=>"ṍ", "Ṏ"=>"ṏ", "Ṑ"=>"ṑ", "Ṓ"=>"ṓ", "Ṕ"=>"ṕ", "Ṗ"=>"ṗ", "Ṙ"=>"ṙ", "Ṛ"=>"ṛ", "Ṝ"=>"ṝ", "Ṟ"=>"ṟ", "Ṡ"=>"ṡ", "Ṣ"=>"ṣ", "Ṥ"=>"ṥ", "Ṧ"=>"ṧ", "Ṩ"=>"ṩ", "Ṫ"=>"ṫ", "Ṭ"=>"ṭ", "Ṯ"=>"ṯ", "Ṱ"=>"ṱ", "Ṳ"=>"ṳ", "Ṵ"=>"ṵ", "Ṷ"=>"ṷ", "Ṹ"=>"ṹ", "Ṻ"=>"ṻ", "Ṽ"=>"ṽ", "Ṿ"=>"ṿ", "Ẁ"=>"ẁ", "Ẃ"=>"ẃ", "Ẅ"=>"ẅ", "Ẇ"=>"ẇ", "Ẉ"=>"ẉ", "Ẋ"=>"ẋ", "Ẍ"=>"ẍ", "Ẏ"=>"ẏ", "Ẑ"=>"ẑ", "Ẓ"=>"ẓ", "Ẕ"=>"ẕ", "ẖ"=>"ẖ", "ẗ"=>"ẗ", "ẘ"=>"ẘ", "ẙ"=>"ẙ", "ẚ"=>"aʾ", "ẛ"=>"ṡ", "Ạ"=>"ạ", "Ả"=>"ả", "Ấ"=>"ấ", "Ầ"=>"ầ", "Ẩ"=>"ẩ", "Ẫ"=>"ẫ", "Ậ"=>"ậ", "Ắ"=>"ắ", "Ằ"=>"ằ", "Ẳ"=>"ẳ", "Ẵ"=>"ẵ", "Ặ"=>"ặ", "Ẹ"=>"ẹ", "Ẻ"=>"ẻ", "Ẽ"=>"ẽ", "Ế"=>"ế", "Ề"=>"ề", "Ể"=>"ể", "Ễ"=>"ễ", "Ệ"=>"ệ", "Ỉ"=>"ỉ", "Ị"=>"ị", "Ọ"=>"ọ", "Ỏ"=>"ỏ", "Ố"=>"ố", "Ồ"=>"ồ", "Ổ"=>"ổ", "Ỗ"=>"ỗ", "Ộ"=>"ộ", "Ớ"=>"ớ", "Ờ"=>"ờ", "Ở"=>"ở", "Ỡ"=>"ỡ", "Ợ"=>"ợ", "Ụ"=>"ụ", "Ủ"=>"ủ", "Ứ"=>"ứ", "Ừ"=>"ừ", "Ử"=>"ử", "Ữ"=>"ữ", "Ự"=>"ự", "Ỳ"=>"ỳ", "Ỵ"=>"ỵ", "Ỷ"=>"ỷ", "Ỹ"=>"ỹ", "Ἀ"=>"ἀ", "Ἁ"=>"ἁ", "Ἂ"=>"ἂ", "Ἃ"=>"ἃ", "Ἄ"=>"ἄ", "Ἅ"=>"ἅ", "Ἆ"=>"ἆ", "Ἇ"=>"ἇ", "Ἐ"=>"ἐ", "Ἑ"=>"ἑ", "Ἒ"=>"ἒ", "Ἓ"=>"ἓ", "Ἔ"=>"ἔ", "Ἕ"=>"ἕ", "Ἠ"=>"ἠ", "Ἡ"=>"ἡ", "Ἢ"=>"ἢ", "Ἣ"=>"ἣ", "Ἤ"=>"ἤ", "Ἥ"=>"ἥ", "Ἦ"=>"ἦ", "Ἧ"=>"ἧ", "Ἰ"=>"ἰ", "Ἱ"=>"ἱ", "Ἲ"=>"ἲ", "Ἳ"=>"ἳ", "Ἴ"=>"ἴ", "Ἵ"=>"ἵ", "Ἶ"=>"ἶ", "Ἷ"=>"ἷ", "Ὀ"=>"ὀ", "Ὁ"=>"ὁ", "Ὂ"=>"ὂ", "Ὃ"=>"ὃ", "Ὄ"=>"ὄ", "Ὅ"=>"ὅ", "ὐ"=>"ὐ", "ὒ"=>"ὒ", "ὔ"=>"ὔ", "ὖ"=>"ὖ", "Ὑ"=>"ὑ", "Ὓ"=>"ὓ", "Ὕ"=>"ὕ", "Ὗ"=>"ὗ", "Ὠ"=>"ὠ", "Ὡ"=>"ὡ", "Ὢ"=>"ὢ", "Ὣ"=>"ὣ", "Ὤ"=>"ὤ", "Ὥ"=>"ὥ", "Ὦ"=>"ὦ", "Ὧ"=>"ὧ", "ᾀ"=>"ἀι", "ᾁ"=>"ἁι", "ᾂ"=>"ἂι", "ᾃ"=>"ἃι", "ᾄ"=>"ἄι", "ᾅ"=>"ἅι", "ᾆ"=>"ἆι", "ᾇ"=>"ἇι", "ᾈ"=>"ἀι", "ᾉ"=>"ἁι", "ᾊ"=>"ἂι", "ᾋ"=>"ἃι", "ᾌ"=>"ἄι", "ᾍ"=>"ἅι", "ᾎ"=>"ἆι", "ᾏ"=>"ἇι", "ᾐ"=>"ἠι", "ᾑ"=>"ἡι", "ᾒ"=>"ἢι", "ᾓ"=>"ἣι", "ᾔ"=>"ἤι", "ᾕ"=>"ἥι", "ᾖ"=>"ἦι", "ᾗ"=>"ἧι", "ᾘ"=>"ἠι", "ᾙ"=>"ἡι", "ᾚ"=>"ἢι", "ᾛ"=>"ἣι", "ᾜ"=>"ἤι", "ᾝ"=>"ἥι", "ᾞ"=>"ἦι", "ᾟ"=>"ἧι", "ᾠ"=>"ὠι", "ᾡ"=>"ὡι", "ᾢ"=>"ὢι", "ᾣ"=>"ὣι", "ᾤ"=>"ὤι", "ᾥ"=>"ὥι", "ᾦ"=>"ὦι", "ᾧ"=>"ὧι", "ᾨ"=>"ὠι", "ᾩ"=>"ὡι", "ᾪ"=>"ὢι", "ᾫ"=>"ὣι", "ᾬ"=>"ὤι", "ᾭ"=>"ὥι", "ᾮ"=>"ὦι", "ᾯ"=>"ὧι", "ᾲ"=>"ὰι", "ᾳ"=>"αι", "ᾴ"=>"άι", "ᾶ"=>"ᾶ", "ᾷ"=>"ᾶι", "Ᾰ"=>"ᾰ", "Ᾱ"=>"ᾱ", "Ὰ"=>"ὰ", "Ά"=>"ά", "ᾼ"=>"αι", "ι"=>"ι", "ῂ"=>"ὴι", "ῃ"=>"ηι", "ῄ"=>"ήι", "ῆ"=>"ῆ", "ῇ"=>"ῆι", "Ὲ"=>"ὲ", "Έ"=>"έ", "Ὴ"=>"ὴ", "Ή"=>"ή", "ῌ"=>"ηι", "ῒ"=>"ῒ", "ΐ"=>"ΐ", "ῖ"=>"ῖ", "ῗ"=>"ῗ", "Ῐ"=>"ῐ", "Ῑ"=>"ῑ", "Ὶ"=>"ὶ", "Ί"=>"ί", "ῢ"=>"ῢ", "ΰ"=>"ΰ", "ῤ"=>"ῤ", "ῦ"=>"ῦ", "ῧ"=>"ῧ", "Ῠ"=>"ῠ", "Ῡ"=>"ῡ", "Ὺ"=>"ὺ", "Ύ"=>"ύ", "Ῥ"=>"ῥ", "ῲ"=>"ὼι", "ῳ"=>"ωι", "ῴ"=>"ώι", "ῶ"=>"ῶ", "ῷ"=>"ῶι", "Ὸ"=>"ὸ", "Ό"=>"ό", "Ὼ"=>"ὼ", "Ώ"=>"ώ", "ῼ"=>"ωι", "₨"=>"rs", "ℂ"=>"c", "℃"=>"°c", "ℇ"=>"ɛ", "℉"=>"°f", "ℋ"=>"h", "ℌ"=>"h", "ℍ"=>"h", "ℐ"=>"i", "ℑ"=>"i", "ℒ"=>"l", "ℕ"=>"n", "№"=>"no", "ℙ"=>"p", "ℚ"=>"q", "ℛ"=>"r", "ℜ"=>"r", "ℝ"=>"r", "℠"=>"sm", "℡"=>"tel", "™"=>"tm", "ℤ"=>"z", "Ω"=>"ω", "ℨ"=>"z", "K"=>"k", "Å"=>"å", "ℬ"=>"b", "ℭ"=>"c", "ℰ"=>"e", "ℱ"=>"f", "ℳ"=>"m", "ℾ"=>"γ", "ℿ"=>"π", "ⅅ"=>"d", "Ⅰ"=>"ⅰ", "Ⅱ"=>"ⅱ", "Ⅲ"=>"ⅲ", "Ⅳ"=>"ⅳ", "Ⅴ"=>"ⅴ", "Ⅵ"=>"ⅵ", "Ⅶ"=>"ⅶ", "Ⅷ"=>"ⅷ", "Ⅸ"=>"ⅸ", "Ⅹ"=>"ⅹ", "Ⅺ"=>"ⅺ", "Ⅻ"=>"ⅻ", "Ⅼ"=>"ⅼ", "Ⅽ"=>"ⅽ", "Ⅾ"=>"ⅾ", "Ⅿ"=>"ⅿ", "Ⓐ"=>"ⓐ", "Ⓑ"=>"ⓑ", "Ⓒ"=>"ⓒ", "Ⓓ"=>"ⓓ", "Ⓔ"=>"ⓔ", "Ⓕ"=>"ⓕ", "Ⓖ"=>"ⓖ", "Ⓗ"=>"ⓗ", "Ⓘ"=>"ⓘ", "Ⓙ"=>"ⓙ", "Ⓚ"=>"ⓚ", "Ⓛ"=>"ⓛ", "Ⓜ"=>"ⓜ", "Ⓝ"=>"ⓝ", "Ⓞ"=>"ⓞ", "Ⓟ"=>"ⓟ", "Ⓠ"=>"ⓠ", "Ⓡ"=>"ⓡ", "Ⓢ"=>"ⓢ", "Ⓣ"=>"ⓣ", "Ⓤ"=>"ⓤ", "Ⓥ"=>"ⓥ", "Ⓦ"=>"ⓦ", "Ⓧ"=>"ⓧ", "Ⓨ"=>"ⓨ", "Ⓩ"=>"ⓩ", "㍱"=>"hpa", "㍳"=>"au", "㍵"=>"ov", "㎀"=>"pa", "㎁"=>"na", "㎂"=>"μa", "㎃"=>"ma", "㎄"=>"ka", "㎅"=>"kb", "㎆"=>"mb", "㎇"=>"gb", "㎊"=>"pf", "㎋"=>"nf", "㎌"=>"μf", "㎐"=>"hz", "㎑"=>"khz", "㎒"=>"mhz", "㎓"=>"ghz", "㎔"=>"thz", "㎩"=>"pa", "㎪"=>"kpa", "㎫"=>"mpa", "㎬"=>"gpa", "㎴"=>"pv", "㎵"=>"nv", "㎶"=>"μv", "㎷"=>"mv", "㎸"=>"kv", "㎹"=>"mv", "㎺"=>"pw", "㎻"=>"nw", "㎼"=>"μw", "㎽"=>"mw", "㎾"=>"kw", "㎿"=>"mw", "㏀"=>"kω", "㏁"=>"mω", "㏃"=>"bq", "㏆"=>"c∕kg", "㏇"=>"co.", "㏈"=>"db", "㏉"=>"gy", "㏋"=>"hp", "㏍"=>"kk", "㏎"=>"km", "㏗"=>"ph", "㏙"=>"ppm", "㏚"=>"pr", "㏜"=>"sv", "㏝"=>"wb", "ff"=>"ff", "fi"=>"fi", "fl"=>"fl", "ffi"=>"ffi", "ffl"=>"ffl", "ſt"=>"st", "st"=>"st", "ﬓ"=>"մն", "ﬔ"=>"մե", "ﬕ"=>"մի", "ﬖ"=>"վն", "ﬗ"=>"մխ", "A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e", "F"=>"f", "G"=>"g", "H"=>"h", "I"=>"i", "J"=>"j", "K"=>"k", "L"=>"l", "M"=>"m", "N"=>"n", "O"=>"o", "P"=>"p", "Q"=>"q", "R"=>"r", "S"=>"s", "T"=>"t", "U"=>"u", "V"=>"v", "W"=>"w", "X"=>"x", "Y"=>"y", "Z"=>"z", "𐐀"=>"𐐨", "𐐁"=>"𐐩", "𐐂"=>"𐐪", "𐐃"=>"𐐫", "𐐄"=>"𐐬", "𐐅"=>"𐐭", "𐐆"=>"𐐮", "𐐇"=>"𐐯", "𐐈"=>"𐐰", "𐐉"=>"𐐱", "𐐊"=>"𐐲", "𐐋"=>"𐐳", "𐐌"=>"𐐴", "𐐍"=>"𐐵", "𐐎"=>"𐐶", "𐐏"=>"𐐷", "𐐐"=>"𐐸", "𐐑"=>"𐐹", "𐐒"=>"𐐺", "𐐓"=>"𐐻", "𐐔"=>"𐐼", "𐐕"=>"𐐽", "𐐖"=>"𐐾", "𐐗"=>"𐐿", "𐐘"=>"𐑀", "𐐙"=>"𐑁", "𐐚"=>"𐑂", "𐐛"=>"𐑃", "𐐜"=>"𐑄", "𐐝"=>"𐑅", "𐐞"=>"𐑆", "𐐟"=>"𐑇", "𐐠"=>"𐑈", "𐐡"=>"𐑉", "𐐢"=>"𐑊", "𐐣"=>"𐑋", "𐐤"=>"𐑌", "𐐥"=>"𐑍", "𝐀"=>"a", "𝐁"=>"b", "𝐂"=>"c", "𝐃"=>"d", "𝐄"=>"e", "𝐅"=>"f", "𝐆"=>"g", "𝐇"=>"h", "𝐈"=>"i", "𝐉"=>"j", "𝐊"=>"k", "𝐋"=>"l", "𝐌"=>"m", "𝐍"=>"n", "𝐎"=>"o", "𝐏"=>"p", "𝐐"=>"q", "𝐑"=>"r", "𝐒"=>"s", "𝐓"=>"t", "𝐔"=>"u", "𝐕"=>"v", "𝐖"=>"w", "𝐗"=>"x", "𝐘"=>"y", "𝐙"=>"z", "𝐴"=>"a", "𝐵"=>"b", "𝐶"=>"c", "𝐷"=>"d", "𝐸"=>"e", "𝐹"=>"f", "𝐺"=>"g", "𝐻"=>"h", "𝐼"=>"i", "𝐽"=>"j", "𝐾"=>"k", "𝐿"=>"l", "𝑀"=>"m", "𝑁"=>"n", "𝑂"=>"o", "𝑃"=>"p", "𝑄"=>"q", "𝑅"=>"r", "𝑆"=>"s", "𝑇"=>"t", "𝑈"=>"u", "𝑉"=>"v", "𝑊"=>"w", "𝑋"=>"x", "𝑌"=>"y", "𝑍"=>"z", "𝑨"=>"a", "𝑩"=>"b", "𝑪"=>"c", "𝑫"=>"d", "𝑬"=>"e", "𝑭"=>"f", "𝑮"=>"g", "𝑯"=>"h", "𝑰"=>"i", "𝑱"=>"j", "𝑲"=>"k", "𝑳"=>"l", "𝑴"=>"m", "𝑵"=>"n", "𝑶"=>"o", "𝑷"=>"p", "𝑸"=>"q", "𝑹"=>"r", "𝑺"=>"s", "𝑻"=>"t", "𝑼"=>"u", "𝑽"=>"v", "𝑾"=>"w", "𝑿"=>"x", "𝒀"=>"y", "𝒁"=>"z", "𝒜"=>"a", "𝒞"=>"c", "𝒟"=>"d", "𝒢"=>"g", "𝒥"=>"j", "𝒦"=>"k", "𝒩"=>"n", "𝒪"=>"o", "𝒫"=>"p", "𝒬"=>"q", "𝒮"=>"s", "𝒯"=>"t", "𝒰"=>"u", "𝒱"=>"v", "𝒲"=>"w", "𝒳"=>"x", "𝒴"=>"y", "𝒵"=>"z", "𝓐"=>"a", "𝓑"=>"b", "𝓒"=>"c", "𝓓"=>"d", "𝓔"=>"e", "𝓕"=>"f", "𝓖"=>"g", "𝓗"=>"h", "𝓘"=>"i", "𝓙"=>"j", "𝓚"=>"k", "𝓛"=>"l", "𝓜"=>"m", "𝓝"=>"n", "𝓞"=>"o", "𝓟"=>"p", "𝓠"=>"q", "𝓡"=>"r", "𝓢"=>"s", "𝓣"=>"t", "𝓤"=>"u", "𝓥"=>"v", "𝓦"=>"w", "𝓧"=>"x", "𝓨"=>"y", "𝓩"=>"z", "𝔄"=>"a", "𝔅"=>"b", "𝔇"=>"d", "𝔈"=>"e", "𝔉"=>"f", "𝔊"=>"g", "𝔍"=>"j", "𝔎"=>"k", "𝔏"=>"l", "𝔐"=>"m", "𝔑"=>"n", "𝔒"=>"o", "𝔓"=>"p", "𝔔"=>"q", "𝔖"=>"s", "𝔗"=>"t", "𝔘"=>"u", "𝔙"=>"v", "𝔚"=>"w", "𝔛"=>"x", "𝔜"=>"y", "𝔸"=>"a", "𝔹"=>"b", "𝔻"=>"d", "𝔼"=>"e", "𝔽"=>"f", "𝔾"=>"g", "𝕀"=>"i", "𝕁"=>"j", "𝕂"=>"k", "𝕃"=>"l", "𝕄"=>"m", "𝕆"=>"o", "𝕊"=>"s", "𝕋"=>"t", "𝕌"=>"u", "𝕍"=>"v", "𝕎"=>"w", "𝕏"=>"x", "𝕐"=>"y", "𝕬"=>"a", "𝕭"=>"b", "𝕮"=>"c", "𝕯"=>"d", "𝕰"=>"e", "𝕱"=>"f", "𝕲"=>"g", "𝕳"=>"h", "𝕴"=>"i", "𝕵"=>"j", "𝕶"=>"k", "𝕷"=>"l", "𝕸"=>"m", "𝕹"=>"n", "𝕺"=>"o", "𝕻"=>"p", "𝕼"=>"q", "𝕽"=>"r", "𝕾"=>"s", "𝕿"=>"t", "𝖀"=>"u", "𝖁"=>"v", "𝖂"=>"w", "𝖃"=>"x", "𝖄"=>"y", "𝖅"=>"z", "𝖠"=>"a", "𝖡"=>"b", "𝖢"=>"c", "𝖣"=>"d", "𝖤"=>"e", "𝖥"=>"f", "𝖦"=>"g", "𝖧"=>"h", "𝖨"=>"i", "𝖩"=>"j", "𝖪"=>"k", "𝖫"=>"l", "𝖬"=>"m", "𝖭"=>"n", "𝖮"=>"o", "𝖯"=>"p", "𝖰"=>"q", "𝖱"=>"r", "𝖲"=>"s", "𝖳"=>"t", "𝖴"=>"u", "𝖵"=>"v", "𝖶"=>"w", "𝖷"=>"x", "𝖸"=>"y", "𝖹"=>"z", "𝗔"=>"a", "𝗕"=>"b", "𝗖"=>"c", "𝗗"=>"d", "𝗘"=>"e", "𝗙"=>"f", "𝗚"=>"g", "𝗛"=>"h", "𝗜"=>"i", "𝗝"=>"j", "𝗞"=>"k", "𝗟"=>"l", "𝗠"=>"m", "𝗡"=>"n", "𝗢"=>"o", "𝗣"=>"p", "𝗤"=>"q", "𝗥"=>"r", "𝗦"=>"s", "𝗧"=>"t", "𝗨"=>"u", "𝗩"=>"v", "𝗪"=>"w", "𝗫"=>"x", "𝗬"=>"y", "𝗭"=>"z", "𝘈"=>"a", "𝘉"=>"b", "𝘊"=>"c", "𝘋"=>"d", "𝘌"=>"e", "𝘍"=>"f", "𝘎"=>"g", "𝘏"=>"h", "𝘐"=>"i", "𝘑"=>"j", "𝘒"=>"k", "𝘓"=>"l", "𝘔"=>"m", "𝘕"=>"n", "𝘖"=>"o", "𝘗"=>"p", "𝘘"=>"q", "𝘙"=>"r", "𝘚"=>"s", "𝘛"=>"t", "𝘜"=>"u", "𝘝"=>"v", "𝘞"=>"w", "𝘟"=>"x", "𝘠"=>"y", "𝘡"=>"z", "𝘼"=>"a", "𝘽"=>"b", "𝘾"=>"c", "𝘿"=>"d", "𝙀"=>"e", "𝙁"=>"f", "𝙂"=>"g", "𝙃"=>"h", "𝙄"=>"i", "𝙅"=>"j", "𝙆"=>"k", "𝙇"=>"l", "𝙈"=>"m", "𝙉"=>"n", "𝙊"=>"o", "𝙋"=>"p", "𝙌"=>"q", "𝙍"=>"r", "𝙎"=>"s", "𝙏"=>"t", "𝙐"=>"u", "𝙑"=>"v", "𝙒"=>"w", "𝙓"=>"x", "𝙔"=>"y", "𝙕"=>"z", "𝙰"=>"a", "𝙱"=>"b", "𝙲"=>"c", "𝙳"=>"d", "𝙴"=>"e", "𝙵"=>"f", "𝙶"=>"g", "𝙷"=>"h", "𝙸"=>"i", "𝙹"=>"j", "𝙺"=>"k", "𝙻"=>"l", "𝙼"=>"m", "𝙽"=>"n", "𝙾"=>"o", "𝙿"=>"p", "𝚀"=>"q", "𝚁"=>"r", "𝚂"=>"s", "𝚃"=>"t", "𝚄"=>"u", "𝚅"=>"v", "𝚆"=>"w", "𝚇"=>"x", "𝚈"=>"y", "𝚉"=>"z", "𝚨"=>"α", "𝚩"=>"β", "𝚪"=>"γ", "𝚫"=>"δ", "𝚬"=>"ε", "𝚭"=>"ζ", "𝚮"=>"η", "𝚯"=>"θ", "𝚰"=>"ι", "𝚱"=>"κ", "𝚲"=>"λ", "𝚳"=>"μ", "𝚴"=>"ν", "𝚵"=>"ξ", "𝚶"=>"ο", "𝚷"=>"π", "𝚸"=>"ρ", "𝚹"=>"θ", "𝚺"=>"σ", "𝚻"=>"τ", "𝚼"=>"υ", "𝚽"=>"φ", "𝚾"=>"χ", "𝚿"=>"ψ", "𝛀"=>"ω", "𝛓"=>"σ", "𝛢"=>"α", "𝛣"=>"β", "𝛤"=>"γ", "𝛥"=>"δ", "𝛦"=>"ε", "𝛧"=>"ζ", "𝛨"=>"η", "𝛩"=>"θ", "𝛪"=>"ι", "𝛫"=>"κ", "𝛬"=>"λ", "𝛭"=>"μ", "𝛮"=>"ν", "𝛯"=>"ξ", "𝛰"=>"ο", "𝛱"=>"π", "𝛲"=>"ρ", "𝛳"=>"θ", "𝛴"=>"σ", "𝛵"=>"τ", "𝛶"=>"υ", "𝛷"=>"φ", "𝛸"=>"χ", "𝛹"=>"ψ", "𝛺"=>"ω", "𝜍"=>"σ", "𝜜"=>"α", "𝜝"=>"β", "𝜞"=>"γ", "𝜟"=>"δ", "𝜠"=>"ε", "𝜡"=>"ζ", "𝜢"=>"η", "𝜣"=>"θ", "𝜤"=>"ι", "𝜥"=>"κ", "𝜦"=>"λ", "𝜧"=>"μ", "𝜨"=>"ν", "𝜩"=>"ξ", "𝜪"=>"ο", "𝜫"=>"π", "𝜬"=>"ρ", "𝜭"=>"θ", "𝜮"=>"σ", "𝜯"=>"τ", "𝜰"=>"υ", "𝜱"=>"φ", "𝜲"=>"χ", "𝜳"=>"ψ", "𝜴"=>"ω", "𝝇"=>"σ", "𝝖"=>"α", "𝝗"=>"β", "𝝘"=>"γ", "𝝙"=>"δ", "𝝚"=>"ε", "𝝛"=>"ζ", "𝝜"=>"η", "𝝝"=>"θ", "𝝞"=>"ι", "𝝟"=>"κ", "𝝠"=>"λ", "𝝡"=>"μ", "𝝢"=>"ν", "𝝣"=>"ξ", "𝝤"=>"ο", "𝝥"=>"π", "𝝦"=>"ρ", "𝝧"=>"θ", "𝝨"=>"σ", "𝝩"=>"τ", "𝝪"=>"υ", "𝝫"=>"φ", "𝝬"=>"χ", "𝝭"=>"ψ", "𝝮"=>"ω", "𝞁"=>"σ", "𝞐"=>"α", "𝞑"=>"β", "𝞒"=>"γ", "𝞓"=>"δ", "𝞔"=>"ε", "𝞕"=>"ζ", "𝞖"=>"η", "𝞗"=>"θ", "𝞘"=>"ι", "𝞙"=>"κ", "𝞚"=>"λ", "𝞛"=>"μ", "𝞜"=>"ν", "𝞝"=>"ξ", "𝞞"=>"ο", "𝞟"=>"π", "𝞠"=>"ρ", "𝞡"=>"θ", "𝞢"=>"σ", "𝞣"=>"τ", "𝞤"=>"υ", "𝞥"=>"φ", "𝞦"=>"χ", "𝞧"=>"ψ", "𝞨"=>"ω", "𝞻"=>"σ"}.freeze - - # Replacements for IN_B.3 - MAP_B_3 = {"A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e", "F"=>"f", "G"=>"g", "H"=>"h", "I"=>"i", "J"=>"j", "K"=>"k", "L"=>"l", "M"=>"m", "N"=>"n", "O"=>"o", "P"=>"p", "Q"=>"q", "R"=>"r", "S"=>"s", "T"=>"t", "U"=>"u", "V"=>"v", "W"=>"w", "X"=>"x", "Y"=>"y", "Z"=>"z", "µ"=>"μ", "À"=>"à", "Á"=>"á", "Â"=>"â", "Ã"=>"ã", "Ä"=>"ä", "Å"=>"å", "Æ"=>"æ", "Ç"=>"ç", "È"=>"è", "É"=>"é", "Ê"=>"ê", "Ë"=>"ë", "Ì"=>"ì", "Í"=>"í", "Î"=>"î", "Ï"=>"ï", "Ð"=>"ð", "Ñ"=>"ñ", "Ò"=>"ò", "Ó"=>"ó", "Ô"=>"ô", "Õ"=>"õ", "Ö"=>"ö", "Ø"=>"ø", "Ù"=>"ù", "Ú"=>"ú", "Û"=>"û", "Ü"=>"ü", "Ý"=>"ý", "Þ"=>"þ", "ß"=>"ss", "Ā"=>"ā", "Ă"=>"ă", "Ą"=>"ą", "Ć"=>"ć", "Ĉ"=>"ĉ", "Ċ"=>"ċ", "Č"=>"č", "Ď"=>"ď", "Đ"=>"đ", "Ē"=>"ē", "Ĕ"=>"ĕ", "Ė"=>"ė", "Ę"=>"ę", "Ě"=>"ě", "Ĝ"=>"ĝ", "Ğ"=>"ğ", "Ġ"=>"ġ", "Ģ"=>"ģ", "Ĥ"=>"ĥ", "Ħ"=>"ħ", "Ĩ"=>"ĩ", "Ī"=>"ī", "Ĭ"=>"ĭ", "Į"=>"į", "İ"=>"i̇", "IJ"=>"ij", "Ĵ"=>"ĵ", "Ķ"=>"ķ", "Ĺ"=>"ĺ", "Ļ"=>"ļ", "Ľ"=>"ľ", "Ŀ"=>"ŀ", "Ł"=>"ł", "Ń"=>"ń", "Ņ"=>"ņ", "Ň"=>"ň", "ʼn"=>"ʼn", "Ŋ"=>"ŋ", "Ō"=>"ō", "Ŏ"=>"ŏ", "Ő"=>"ő", "Œ"=>"œ", "Ŕ"=>"ŕ", "Ŗ"=>"ŗ", "Ř"=>"ř", "Ś"=>"ś", "Ŝ"=>"ŝ", "Ş"=>"ş", "Š"=>"š", "Ţ"=>"ţ", "Ť"=>"ť", "Ŧ"=>"ŧ", "Ũ"=>"ũ", "Ū"=>"ū", "Ŭ"=>"ŭ", "Ů"=>"ů", "Ű"=>"ű", "Ų"=>"ų", "Ŵ"=>"ŵ", "Ŷ"=>"ŷ", "Ÿ"=>"ÿ", "Ź"=>"ź", "Ż"=>"ż", "Ž"=>"ž", "ſ"=>"s", "Ɓ"=>"ɓ", "Ƃ"=>"ƃ", "Ƅ"=>"ƅ", "Ɔ"=>"ɔ", "Ƈ"=>"ƈ", "Ɖ"=>"ɖ", "Ɗ"=>"ɗ", "Ƌ"=>"ƌ", "Ǝ"=>"ǝ", "Ə"=>"ə", "Ɛ"=>"ɛ", "Ƒ"=>"ƒ", "Ɠ"=>"ɠ", "Ɣ"=>"ɣ", "Ɩ"=>"ɩ", "Ɨ"=>"ɨ", "Ƙ"=>"ƙ", "Ɯ"=>"ɯ", "Ɲ"=>"ɲ", "Ɵ"=>"ɵ", "Ơ"=>"ơ", "Ƣ"=>"ƣ", "Ƥ"=>"ƥ", "Ʀ"=>"ʀ", "Ƨ"=>"ƨ", "Ʃ"=>"ʃ", "Ƭ"=>"ƭ", "Ʈ"=>"ʈ", "Ư"=>"ư", "Ʊ"=>"ʊ", "Ʋ"=>"ʋ", "Ƴ"=>"ƴ", "Ƶ"=>"ƶ", "Ʒ"=>"ʒ", "Ƹ"=>"ƹ", "Ƽ"=>"ƽ", "DŽ"=>"dž", "Dž"=>"dž", "LJ"=>"lj", "Lj"=>"lj", "NJ"=>"nj", "Nj"=>"nj", "Ǎ"=>"ǎ", "Ǐ"=>"ǐ", "Ǒ"=>"ǒ", "Ǔ"=>"ǔ", "Ǖ"=>"ǖ", "Ǘ"=>"ǘ", "Ǚ"=>"ǚ", "Ǜ"=>"ǜ", "Ǟ"=>"ǟ", "Ǡ"=>"ǡ", "Ǣ"=>"ǣ", "Ǥ"=>"ǥ", "Ǧ"=>"ǧ", "Ǩ"=>"ǩ", "Ǫ"=>"ǫ", "Ǭ"=>"ǭ", "Ǯ"=>"ǯ", "ǰ"=>"ǰ", "DZ"=>"dz", "Dz"=>"dz", "Ǵ"=>"ǵ", "Ƕ"=>"ƕ", "Ƿ"=>"ƿ", "Ǹ"=>"ǹ", "Ǻ"=>"ǻ", "Ǽ"=>"ǽ", "Ǿ"=>"ǿ", "Ȁ"=>"ȁ", "Ȃ"=>"ȃ", "Ȅ"=>"ȅ", "Ȇ"=>"ȇ", "Ȉ"=>"ȉ", "Ȋ"=>"ȋ", "Ȍ"=>"ȍ", "Ȏ"=>"ȏ", "Ȑ"=>"ȑ", "Ȓ"=>"ȓ", "Ȕ"=>"ȕ", "Ȗ"=>"ȗ", "Ș"=>"ș", "Ț"=>"ț", "Ȝ"=>"ȝ", "Ȟ"=>"ȟ", "Ƞ"=>"ƞ", "Ȣ"=>"ȣ", "Ȥ"=>"ȥ", "Ȧ"=>"ȧ", "Ȩ"=>"ȩ", "Ȫ"=>"ȫ", "Ȭ"=>"ȭ", "Ȯ"=>"ȯ", "Ȱ"=>"ȱ", "Ȳ"=>"ȳ", "ͅ"=>"ι", "Ά"=>"ά", "Έ"=>"έ", "Ή"=>"ή", "Ί"=>"ί", "Ό"=>"ό", "Ύ"=>"ύ", "Ώ"=>"ώ", "ΐ"=>"ΐ", "Α"=>"α", "Β"=>"β", "Γ"=>"γ", "Δ"=>"δ", "Ε"=>"ε", "Ζ"=>"ζ", "Η"=>"η", "Θ"=>"θ", "Ι"=>"ι", "Κ"=>"κ", "Λ"=>"λ", "Μ"=>"μ", "Ν"=>"ν", "Ξ"=>"ξ", "Ο"=>"ο", "Π"=>"π", "Ρ"=>"ρ", "Σ"=>"σ", "Τ"=>"τ", "Υ"=>"υ", "Φ"=>"φ", "Χ"=>"χ", "Ψ"=>"ψ", "Ω"=>"ω", "Ϊ"=>"ϊ", "Ϋ"=>"ϋ", "ΰ"=>"ΰ", "ς"=>"σ", "ϐ"=>"β", "ϑ"=>"θ", "ϕ"=>"φ", "ϖ"=>"π", "Ϙ"=>"ϙ", "Ϛ"=>"ϛ", "Ϝ"=>"ϝ", "Ϟ"=>"ϟ", "Ϡ"=>"ϡ", "Ϣ"=>"ϣ", "Ϥ"=>"ϥ", "Ϧ"=>"ϧ", "Ϩ"=>"ϩ", "Ϫ"=>"ϫ", "Ϭ"=>"ϭ", "Ϯ"=>"ϯ", "ϰ"=>"κ", "ϱ"=>"ρ", "ϲ"=>"σ", "ϴ"=>"θ", "ϵ"=>"ε", "Ѐ"=>"ѐ", "Ё"=>"ё", "Ђ"=>"ђ", "Ѓ"=>"ѓ", "Є"=>"є", "Ѕ"=>"ѕ", "І"=>"і", "Ї"=>"ї", "Ј"=>"ј", "Љ"=>"љ", "Њ"=>"њ", "Ћ"=>"ћ", "Ќ"=>"ќ", "Ѝ"=>"ѝ", "Ў"=>"ў", "Џ"=>"џ", "А"=>"а", "Б"=>"б", "В"=>"в", "Г"=>"г", "Д"=>"д", "Е"=>"е", "Ж"=>"ж", "З"=>"з", "И"=>"и", "Й"=>"й", "К"=>"к", "Л"=>"л", "М"=>"м", "Н"=>"н", "О"=>"о", "П"=>"п", "Р"=>"р", "С"=>"с", "Т"=>"т", "У"=>"у", "Ф"=>"ф", "Х"=>"х", "Ц"=>"ц", "Ч"=>"ч", "Ш"=>"ш", "Щ"=>"щ", "Ъ"=>"ъ", "Ы"=>"ы", "Ь"=>"ь", "Э"=>"э", "Ю"=>"ю", "Я"=>"я", "Ѡ"=>"ѡ", "Ѣ"=>"ѣ", "Ѥ"=>"ѥ", "Ѧ"=>"ѧ", "Ѩ"=>"ѩ", "Ѫ"=>"ѫ", "Ѭ"=>"ѭ", "Ѯ"=>"ѯ", "Ѱ"=>"ѱ", "Ѳ"=>"ѳ", "Ѵ"=>"ѵ", "Ѷ"=>"ѷ", "Ѹ"=>"ѹ", "Ѻ"=>"ѻ", "Ѽ"=>"ѽ", "Ѿ"=>"ѿ", "Ҁ"=>"ҁ", "Ҋ"=>"ҋ", "Ҍ"=>"ҍ", "Ҏ"=>"ҏ", "Ґ"=>"ґ", "Ғ"=>"ғ", "Ҕ"=>"ҕ", "Җ"=>"җ", "Ҙ"=>"ҙ", "Қ"=>"қ", "Ҝ"=>"ҝ", "Ҟ"=>"ҟ", "Ҡ"=>"ҡ", "Ң"=>"ң", "Ҥ"=>"ҥ", "Ҧ"=>"ҧ", "Ҩ"=>"ҩ", "Ҫ"=>"ҫ", "Ҭ"=>"ҭ", "Ү"=>"ү", "Ұ"=>"ұ", "Ҳ"=>"ҳ", "Ҵ"=>"ҵ", "Ҷ"=>"ҷ", "Ҹ"=>"ҹ", "Һ"=>"һ", "Ҽ"=>"ҽ", "Ҿ"=>"ҿ", "Ӂ"=>"ӂ", "Ӄ"=>"ӄ", "Ӆ"=>"ӆ", "Ӈ"=>"ӈ", "Ӊ"=>"ӊ", "Ӌ"=>"ӌ", "Ӎ"=>"ӎ", "Ӑ"=>"ӑ", "Ӓ"=>"ӓ", "Ӕ"=>"ӕ", "Ӗ"=>"ӗ", "Ә"=>"ә", "Ӛ"=>"ӛ", "Ӝ"=>"ӝ", "Ӟ"=>"ӟ", "Ӡ"=>"ӡ", "Ӣ"=>"ӣ", "Ӥ"=>"ӥ", "Ӧ"=>"ӧ", "Ө"=>"ө", "Ӫ"=>"ӫ", "Ӭ"=>"ӭ", "Ӯ"=>"ӯ", "Ӱ"=>"ӱ", "Ӳ"=>"ӳ", "Ӵ"=>"ӵ", "Ӹ"=>"ӹ", "Ԁ"=>"ԁ", "Ԃ"=>"ԃ", "Ԅ"=>"ԅ", "Ԇ"=>"ԇ", "Ԉ"=>"ԉ", "Ԋ"=>"ԋ", "Ԍ"=>"ԍ", "Ԏ"=>"ԏ", "Ա"=>"ա", "Բ"=>"բ", "Գ"=>"գ", "Դ"=>"դ", "Ե"=>"ե", "Զ"=>"զ", "Է"=>"է", "Ը"=>"ը", "Թ"=>"թ", "Ժ"=>"ժ", "Ի"=>"ի", "Լ"=>"լ", "Խ"=>"խ", "Ծ"=>"ծ", "Կ"=>"կ", "Հ"=>"հ", "Ձ"=>"ձ", "Ղ"=>"ղ", "Ճ"=>"ճ", "Մ"=>"մ", "Յ"=>"յ", "Ն"=>"ն", "Շ"=>"շ", "Ո"=>"ո", "Չ"=>"չ", "Պ"=>"պ", "Ջ"=>"ջ", "Ռ"=>"ռ", "Ս"=>"ս", "Վ"=>"վ", "Տ"=>"տ", "Ր"=>"ր", "Ց"=>"ց", "Ւ"=>"ւ", "Փ"=>"փ", "Ք"=>"ք", "Օ"=>"օ", "Ֆ"=>"ֆ", "և"=>"եւ", "Ḁ"=>"ḁ", "Ḃ"=>"ḃ", "Ḅ"=>"ḅ", "Ḇ"=>"ḇ", "Ḉ"=>"ḉ", "Ḋ"=>"ḋ", "Ḍ"=>"ḍ", "Ḏ"=>"ḏ", "Ḑ"=>"ḑ", "Ḓ"=>"ḓ", "Ḕ"=>"ḕ", "Ḗ"=>"ḗ", "Ḙ"=>"ḙ", "Ḛ"=>"ḛ", "Ḝ"=>"ḝ", "Ḟ"=>"ḟ", "Ḡ"=>"ḡ", "Ḣ"=>"ḣ", "Ḥ"=>"ḥ", "Ḧ"=>"ḧ", "Ḩ"=>"ḩ", "Ḫ"=>"ḫ", "Ḭ"=>"ḭ", "Ḯ"=>"ḯ", "Ḱ"=>"ḱ", "Ḳ"=>"ḳ", "Ḵ"=>"ḵ", "Ḷ"=>"ḷ", "Ḹ"=>"ḹ", "Ḻ"=>"ḻ", "Ḽ"=>"ḽ", "Ḿ"=>"ḿ", "Ṁ"=>"ṁ", "Ṃ"=>"ṃ", "Ṅ"=>"ṅ", "Ṇ"=>"ṇ", "Ṉ"=>"ṉ", "Ṋ"=>"ṋ", "Ṍ"=>"ṍ", "Ṏ"=>"ṏ", "Ṑ"=>"ṑ", "Ṓ"=>"ṓ", "Ṕ"=>"ṕ", "Ṗ"=>"ṗ", "Ṙ"=>"ṙ", "Ṛ"=>"ṛ", "Ṝ"=>"ṝ", "Ṟ"=>"ṟ", "Ṡ"=>"ṡ", "Ṣ"=>"ṣ", "Ṥ"=>"ṥ", "Ṧ"=>"ṧ", "Ṩ"=>"ṩ", "Ṫ"=>"ṫ", "Ṭ"=>"ṭ", "Ṯ"=>"ṯ", "Ṱ"=>"ṱ", "Ṳ"=>"ṳ", "Ṵ"=>"ṵ", "Ṷ"=>"ṷ", "Ṹ"=>"ṹ", "Ṻ"=>"ṻ", "Ṽ"=>"ṽ", "Ṿ"=>"ṿ", "Ẁ"=>"ẁ", "Ẃ"=>"ẃ", "Ẅ"=>"ẅ", "Ẇ"=>"ẇ", "Ẉ"=>"ẉ", "Ẋ"=>"ẋ", "Ẍ"=>"ẍ", "Ẏ"=>"ẏ", "Ẑ"=>"ẑ", "Ẓ"=>"ẓ", "Ẕ"=>"ẕ", "ẖ"=>"ẖ", "ẗ"=>"ẗ", "ẘ"=>"ẘ", "ẙ"=>"ẙ", "ẚ"=>"aʾ", "ẛ"=>"ṡ", "Ạ"=>"ạ", "Ả"=>"ả", "Ấ"=>"ấ", "Ầ"=>"ầ", "Ẩ"=>"ẩ", "Ẫ"=>"ẫ", "Ậ"=>"ậ", "Ắ"=>"ắ", "Ằ"=>"ằ", "Ẳ"=>"ẳ", "Ẵ"=>"ẵ", "Ặ"=>"ặ", "Ẹ"=>"ẹ", "Ẻ"=>"ẻ", "Ẽ"=>"ẽ", "Ế"=>"ế", "Ề"=>"ề", "Ể"=>"ể", "Ễ"=>"ễ", "Ệ"=>"ệ", "Ỉ"=>"ỉ", "Ị"=>"ị", "Ọ"=>"ọ", "Ỏ"=>"ỏ", "Ố"=>"ố", "Ồ"=>"ồ", "Ổ"=>"ổ", "Ỗ"=>"ỗ", "Ộ"=>"ộ", "Ớ"=>"ớ", "Ờ"=>"ờ", "Ở"=>"ở", "Ỡ"=>"ỡ", "Ợ"=>"ợ", "Ụ"=>"ụ", "Ủ"=>"ủ", "Ứ"=>"ứ", "Ừ"=>"ừ", "Ử"=>"ử", "Ữ"=>"ữ", "Ự"=>"ự", "Ỳ"=>"ỳ", "Ỵ"=>"ỵ", "Ỷ"=>"ỷ", "Ỹ"=>"ỹ", "Ἀ"=>"ἀ", "Ἁ"=>"ἁ", "Ἂ"=>"ἂ", "Ἃ"=>"ἃ", "Ἄ"=>"ἄ", "Ἅ"=>"ἅ", "Ἆ"=>"ἆ", "Ἇ"=>"ἇ", "Ἐ"=>"ἐ", "Ἑ"=>"ἑ", "Ἒ"=>"ἒ", "Ἓ"=>"ἓ", "Ἔ"=>"ἔ", "Ἕ"=>"ἕ", "Ἠ"=>"ἠ", "Ἡ"=>"ἡ", "Ἢ"=>"ἢ", "Ἣ"=>"ἣ", "Ἤ"=>"ἤ", "Ἥ"=>"ἥ", "Ἦ"=>"ἦ", "Ἧ"=>"ἧ", "Ἰ"=>"ἰ", "Ἱ"=>"ἱ", "Ἲ"=>"ἲ", "Ἳ"=>"ἳ", "Ἴ"=>"ἴ", "Ἵ"=>"ἵ", "Ἶ"=>"ἶ", "Ἷ"=>"ἷ", "Ὀ"=>"ὀ", "Ὁ"=>"ὁ", "Ὂ"=>"ὂ", "Ὃ"=>"ὃ", "Ὄ"=>"ὄ", "Ὅ"=>"ὅ", "ὐ"=>"ὐ", "ὒ"=>"ὒ", "ὔ"=>"ὔ", "ὖ"=>"ὖ", "Ὑ"=>"ὑ", "Ὓ"=>"ὓ", "Ὕ"=>"ὕ", "Ὗ"=>"ὗ", "Ὠ"=>"ὠ", "Ὡ"=>"ὡ", "Ὢ"=>"ὢ", "Ὣ"=>"ὣ", "Ὤ"=>"ὤ", "Ὥ"=>"ὥ", "Ὦ"=>"ὦ", "Ὧ"=>"ὧ", "ᾀ"=>"ἀι", "ᾁ"=>"ἁι", "ᾂ"=>"ἂι", "ᾃ"=>"ἃι", "ᾄ"=>"ἄι", "ᾅ"=>"ἅι", "ᾆ"=>"ἆι", "ᾇ"=>"ἇι", "ᾈ"=>"ἀι", "ᾉ"=>"ἁι", "ᾊ"=>"ἂι", "ᾋ"=>"ἃι", "ᾌ"=>"ἄι", "ᾍ"=>"ἅι", "ᾎ"=>"ἆι", "ᾏ"=>"ἇι", "ᾐ"=>"ἠι", "ᾑ"=>"ἡι", "ᾒ"=>"ἢι", "ᾓ"=>"ἣι", "ᾔ"=>"ἤι", "ᾕ"=>"ἥι", "ᾖ"=>"ἦι", "ᾗ"=>"ἧι", "ᾘ"=>"ἠι", "ᾙ"=>"ἡι", "ᾚ"=>"ἢι", "ᾛ"=>"ἣι", "ᾜ"=>"ἤι", "ᾝ"=>"ἥι", "ᾞ"=>"ἦι", "ᾟ"=>"ἧι", "ᾠ"=>"ὠι", "ᾡ"=>"ὡι", "ᾢ"=>"ὢι", "ᾣ"=>"ὣι", "ᾤ"=>"ὤι", "ᾥ"=>"ὥι", "ᾦ"=>"ὦι", "ᾧ"=>"ὧι", "ᾨ"=>"ὠι", "ᾩ"=>"ὡι", "ᾪ"=>"ὢι", "ᾫ"=>"ὣι", "ᾬ"=>"ὤι", "ᾭ"=>"ὥι", "ᾮ"=>"ὦι", "ᾯ"=>"ὧι", "ᾲ"=>"ὰι", "ᾳ"=>"αι", "ᾴ"=>"άι", "ᾶ"=>"ᾶ", "ᾷ"=>"ᾶι", "Ᾰ"=>"ᾰ", "Ᾱ"=>"ᾱ", "Ὰ"=>"ὰ", "Ά"=>"ά", "ᾼ"=>"αι", "ι"=>"ι", "ῂ"=>"ὴι", "ῃ"=>"ηι", "ῄ"=>"ήι", "ῆ"=>"ῆ", "ῇ"=>"ῆι", "Ὲ"=>"ὲ", "Έ"=>"έ", "Ὴ"=>"ὴ", "Ή"=>"ή", "ῌ"=>"ηι", "ῒ"=>"ῒ", "ΐ"=>"ΐ", "ῖ"=>"ῖ", "ῗ"=>"ῗ", "Ῐ"=>"ῐ", "Ῑ"=>"ῑ", "Ὶ"=>"ὶ", "Ί"=>"ί", "ῢ"=>"ῢ", "ΰ"=>"ΰ", "ῤ"=>"ῤ", "ῦ"=>"ῦ", "ῧ"=>"ῧ", "Ῠ"=>"ῠ", "Ῡ"=>"ῡ", "Ὺ"=>"ὺ", "Ύ"=>"ύ", "Ῥ"=>"ῥ", "ῲ"=>"ὼι", "ῳ"=>"ωι", "ῴ"=>"ώι", "ῶ"=>"ῶ", "ῷ"=>"ῶι", "Ὸ"=>"ὸ", "Ό"=>"ό", "Ὼ"=>"ὼ", "Ώ"=>"ώ", "ῼ"=>"ωι", "Ω"=>"ω", "K"=>"k", "Å"=>"å", "Ⅰ"=>"ⅰ", "Ⅱ"=>"ⅱ", "Ⅲ"=>"ⅲ", "Ⅳ"=>"ⅳ", "Ⅴ"=>"ⅴ", "Ⅵ"=>"ⅵ", "Ⅶ"=>"ⅶ", "Ⅷ"=>"ⅷ", "Ⅸ"=>"ⅸ", "Ⅹ"=>"ⅹ", "Ⅺ"=>"ⅺ", "Ⅻ"=>"ⅻ", "Ⅼ"=>"ⅼ", "Ⅽ"=>"ⅽ", "Ⅾ"=>"ⅾ", "Ⅿ"=>"ⅿ", "Ⓐ"=>"ⓐ", "Ⓑ"=>"ⓑ", "Ⓒ"=>"ⓒ", "Ⓓ"=>"ⓓ", "Ⓔ"=>"ⓔ", "Ⓕ"=>"ⓕ", "Ⓖ"=>"ⓖ", "Ⓗ"=>"ⓗ", "Ⓘ"=>"ⓘ", "Ⓙ"=>"ⓙ", "Ⓚ"=>"ⓚ", "Ⓛ"=>"ⓛ", "Ⓜ"=>"ⓜ", "Ⓝ"=>"ⓝ", "Ⓞ"=>"ⓞ", "Ⓟ"=>"ⓟ", "Ⓠ"=>"ⓠ", "Ⓡ"=>"ⓡ", "Ⓢ"=>"ⓢ", "Ⓣ"=>"ⓣ", "Ⓤ"=>"ⓤ", "Ⓥ"=>"ⓥ", "Ⓦ"=>"ⓦ", "Ⓧ"=>"ⓧ", "Ⓨ"=>"ⓨ", "Ⓩ"=>"ⓩ", "ff"=>"ff", "fi"=>"fi", "fl"=>"fl", "ffi"=>"ffi", "ffl"=>"ffl", "ſt"=>"st", "st"=>"st", "ﬓ"=>"մն", "ﬔ"=>"մե", "ﬕ"=>"մի", "ﬖ"=>"վն", "ﬗ"=>"մխ", "A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e", "F"=>"f", "G"=>"g", "H"=>"h", "I"=>"i", "J"=>"j", "K"=>"k", "L"=>"l", "M"=>"m", "N"=>"n", "O"=>"o", "P"=>"p", "Q"=>"q", "R"=>"r", "S"=>"s", "T"=>"t", "U"=>"u", "V"=>"v", "W"=>"w", "X"=>"x", "Y"=>"y", "Z"=>"z", "𐐀"=>"𐐨", "𐐁"=>"𐐩", "𐐂"=>"𐐪", "𐐃"=>"𐐫", "𐐄"=>"𐐬", "𐐅"=>"𐐭", "𐐆"=>"𐐮", "𐐇"=>"𐐯", "𐐈"=>"𐐰", "𐐉"=>"𐐱", "𐐊"=>"𐐲", "𐐋"=>"𐐳", "𐐌"=>"𐐴", "𐐍"=>"𐐵", "𐐎"=>"𐐶", "𐐏"=>"𐐷", "𐐐"=>"𐐸", "𐐑"=>"𐐹", "𐐒"=>"𐐺", "𐐓"=>"𐐻", "𐐔"=>"𐐼", "𐐕"=>"𐐽", "𐐖"=>"𐐾", "𐐗"=>"𐐿", "𐐘"=>"𐑀", "𐐙"=>"𐑁", "𐐚"=>"𐑂", "𐐛"=>"𐑃", "𐐜"=>"𐑄", "𐐝"=>"𐑅", "𐐞"=>"𐑆", "𐐟"=>"𐑇", "𐐠"=>"𐑈", "𐐡"=>"𐑉", "𐐢"=>"𐑊", "𐐣"=>"𐑋", "𐐤"=>"𐑌", "𐐥"=>"𐑍"}.freeze - - # ASCII space characters \StringPrep\[\"C.1.1\"] - IN_C_1_1 = / /.freeze - - # Non-ASCII space characters \StringPrep\[\"C.1.2\"] - IN_C_1_2 = /[\u200b\p{Zs}&&[^ ]]/.freeze - - # ASCII control characters \StringPrep\[\"C.2.1\"] - IN_C_2_1 = /[\x00-\x1f\x7f]/.freeze - - # Non-ASCII control characters \StringPrep\[\"C.2.2\"] - IN_C_2_2 = /[\u{06dd 070f 180e feff}\u{0080}-\u{009f}\u{200c}-\u{200d}\u{2028}-\u{2029}\u{2060}-\u{2063}\u{206a}-\u{206f}\u{fff9}-\u{fffc}\u{1d173}-\u{1d17a}]/.freeze - - # Private use \StringPrep\[\"C.3\"] - IN_C_3 = /\p{private use}/.freeze - - # Non-character code points \StringPrep\[\"C.4\"] - IN_C_4 = /\p{noncharacter code point}/.freeze - - # Surrogate codes \StringPrep\[\"C.5\"] - IN_C_5 = /\p{surrogate}/.freeze - - # Inappropriate for plain text \StringPrep\[\"C.6\"] - IN_C_6 = /[\p{in specials}&&\p{AGE=3.2}&&\p{^NChar}]/.freeze - - # Inappropriate for canonical representation \StringPrep\[\"C.7\"] - IN_C_7 = /[\p{in ideographic description characters}&&\p{AGE=3.2}]/.freeze - - # Change display properties or are deprecated \StringPrep\[\"C.8\"] - IN_C_8 = /[\u{0340}-\u{0341}\u{200e}-\u{200f}\u{202a}-\u{202e}\u{206a}-\u{206f}]/.freeze - - # Tagging characters \StringPrep\[\"C.9\"] - IN_C_9 = /[\p{in Tags}&&\p{AGE=3.2}]/.freeze - - # Characters with bidirectional property "R" or "AL" \StringPrep\[\"D.1\"] - IN_D_1 = /[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]/.freeze - - # Used to check req3 of bidirectional checks - # Matches the negation of the D.1 table - IN_D_1_NEGATED = /[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]/.freeze - - # Characters with bidirectional property "L" \StringPrep\[\"D.2\"] - IN_D_2 = /[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]/.freeze - - BIDI_DESC_REQ2 = "A string with RandALCat characters must not contain LCat characters." - - # Bidirectional Characters [StringPrep, §6], Requirement 2 - # >>> - # If a string contains any RandALCat character, the string MUST NOT - # contain any LCat character. - BIDI_FAILS_REQ2 = /(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]))|(?m-ix:(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))/.freeze - - BIDI_DESC_REQ3 = "A string with RandALCat characters must start and end with RandALCat characters." - - # Bidirectional Characters [StringPrep, §6], Requirement 3 - # >>> - # If a string contains any RandALCat character, a RandALCat - # character MUST be the first character of the string, and a - # RandALCat character MUST be the last character of the string. - BIDI_FAILS_REQ3 = /(?m-ix:\A(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))|(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])\z)/.freeze - - # Bidirectional Characters [StringPrep, §6] - BIDI_FAILURE = /(?-mix:(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]))|(?m-ix:(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])))|(?-mix:(?m-ix:\A(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))|(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])\z))/.freeze - - # Names of each codepoint table in the RFC-3454 appendices - TITLES = { - "A.1" => "Unassigned code points in Unicode 3.2", - "B.1" => "Commonly mapped to nothing", - "B.2" => "Mapping for case-folding used with NFKC", - "B.3" => "Mapping for case-folding used with no normalization", - "C.1" => "Space characters", - "C.1.1" => "ASCII space characters", - "C.1.2" => "Non-ASCII space characters", - "C.2" => "Control characters", - "C.2.1" => "ASCII control characters", - "C.2.2" => "Non-ASCII control characters", - "C.3" => "Private use", - "C.4" => "Non-character code points", - "C.5" => "Surrogate codes", - "C.6" => "Inappropriate for plain text", - "C.7" => "Inappropriate for canonical representation", - "C.8" => "Change display properties or are deprecated", - "C.9" => "Tagging characters", - "D.1" => "Characters with bidirectional property \"R\" or \"AL\"", - "D.2" => "Characters with bidirectional property \"L\"", - }.freeze - - # Regexps matching each codepoint table in the RFC-3454 appendices - REGEXPS = { - "A.1" => IN_A_1, - "B.1" => IN_B_1, - "B.2" => IN_B_2, - "B.3" => IN_B_3, - "C.1.1" => IN_C_1_1, - "C.1.2" => IN_C_1_2, - "C.2.1" => IN_C_2_1, - "C.2.2" => IN_C_2_2, - "C.3" => IN_C_3, - "C.4" => IN_C_4, - "C.5" => IN_C_5, - "C.6" => IN_C_6, - "C.7" => IN_C_7, - "C.8" => IN_C_8, - "C.9" => IN_C_9, - "D.1" => IN_D_1, - "D.2" => IN_D_2, - }.freeze - - MAPPINGS = { - "B.1" => [IN_B_1, MAP_B_1].freeze, - "B.2" => [IN_B_2, MAP_B_2].freeze, - "B.3" => [IN_B_3, MAP_B_3].freeze, - }.freeze - - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/trace.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/trace.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/trace.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep/trace.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP - module StringPrep - - # Defined in RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The +trace+ - # profile of \StringPrep is used by the +ANONYMOUS+ \SASL mechanism. - module Trace - - # Defined in RFC-4505[https://tools.ietf.org/html/rfc4505] §3. - STRINGPREP_PROFILE = "trace" - - # >>> - # The character repertoire of this profile is Unicode 3.2 [Unicode]. - UNASSIGNED_TABLE = "A.1" - - # >>> - # No mapping is required by this profile. - MAPPING_TABLES = nil - - # >>> - # No Unicode normalization is required by this profile. - NORMALIZATION = nil - - # From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace" - # Profile of "Stringprep": - # >>> - # Characters from the following tables of [StringPrep] are prohibited: - # - # - C.2.1 (ASCII control characters) - # - C.2.2 (Non-ASCII control characters) - # - C.3 (Private use characters) - # - C.4 (Non-character code points) - # - C.5 (Surrogate codes) - # - C.6 (Inappropriate for plain text) - # - C.8 (Change display properties are deprecated) - # - C.9 (Tagging characters) - # - # No additional characters are prohibited. - PROHIBITED_TABLES = %w[C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.8 C.9].freeze - - # >>> - # This profile requires bidirectional character checking per Section 6 - # of [StringPrep]. - CHECK_BIDI = true - - module_function - - # From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace" - # Profile of "Stringprep": - # >>> - # The character repertoire of this profile is Unicode 3.2 [Unicode]. - # - # No mapping is required by this profile. - # - # No Unicode normalization is required by this profile. - # - # The list of unassigned code points for this profile is that provided - # in Appendix A of [StringPrep]. Unassigned code points are not - # prohibited. - # - # Characters from the following tables of [StringPrep] are prohibited: - # (documented on PROHIBITED_TABLES) - # - # This profile requires bidirectional character checking per Section 6 - # of [StringPrep]. - def stringprep_trace(string, **opts) - StringPrep.stringprep( - string, - unassigned: UNASSIGNED_TABLE, - maps: MAPPING_TABLES, - prohibited: PROHIBITED_TABLES, - normalization: NORMALIZATION, - bidi: CHECK_BIDI, - profile: STRINGPREP_PROFILE, - **opts, - ) - end - - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap/stringprep.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,159 +0,0 @@ -# frozen_string_literal: true - -module Net - class IMAP < Protocol - - # Regexps and utility methods for implementing stringprep profiles. The - # \StringPrep algorithm is defined by - # {RFC-3454}[https://www.rfc-editor.org/rfc/rfc3454.html]. Each - # codepoint table defined in the RFC-3454 appendices is matched by a Regexp - # defined in this module. - module StringPrep - autoload :NamePrep, File.expand_path("stringprep/nameprep", __dir__) - autoload :SASLprep, File.expand_path("stringprep/saslprep", __dir__) - autoload :Tables, File.expand_path("stringprep/tables", __dir__) - autoload :Trace, File.expand_path("stringprep/trace", __dir__) - - # ArgumentError raised when +string+ is invalid for the stringprep - # +profile+. - class StringPrepError < ArgumentError - attr_reader :string, :profile - - def initialize(*args, string: nil, profile: nil) - @string = -string.to_str unless string.nil? - @profile = -profile.to_str unless profile.nil? - super(*args) - end - end - - # StringPrepError raised when +string+ contains a codepoint prohibited by - # +table+. - class ProhibitedCodepoint < StringPrepError - attr_reader :table - - def initialize(table, *args, **kwargs) - @table = table - details = (title = Tables::TITLES[table]) ? - "%s [%s]" % [title, table] : table - message = "String contains a prohibited codepoint: %s" % [details] - super(message, *args, **kwargs) - end - end - - # StringPrepError raised when +string+ contains bidirectional characters - # which violate the StringPrep requirements. - class BidiStringError < StringPrepError - end - - # Returns a Regexp matching the given +table+ name. - def self.[](table) - Tables::REGEXPS.fetch(table) - end - - module_function - - # >>> - # 1. Map -- For each character in the input, check if it has a mapping - # and, if so, replace it with its mapping. This is described in - # section 3. - # - # 2. Normalize -- Possibly normalize the result of step 1 using Unicode - # normalization. This is described in section 4. - # - # 3. Prohibit -- Check for any characters that are not allowed in the - # output. If any are found, return an error. This is described in - # section 5. - # - # 4. Check bidi -- Possibly check for right-to-left characters, and if - # any are found, make sure that the whole string satisfies the - # requirements for bidirectional strings. If the string does not - # satisfy the requirements for bidirectional strings, return an - # error. This is described in section 6. - # - # The above steps MUST be performed in the order given to comply with - # this specification. - # - def stringprep(string, - maps:, - normalization:, - prohibited:, - **opts) - string = string.encode("UTF-8") # also dups (and raises invalid encoding) - map_tables!(string, *maps) if maps - string.unicode_normalize!(normalization) if normalization - check_prohibited!(string, *prohibited, **opts) if prohibited - string - end - - def map_tables!(string, *tables) - tables.each do |table| - regexp, replacements = Tables::MAPPINGS.fetch(table) - string.gsub!(regexp, replacements) - end - string - end - - # Checks +string+ for any codepoint in +tables+. Raises a - # ProhibitedCodepoint describing the first matching table. - # - # Also checks bidirectional characters, when bidi: true, which may - # raise a BidiStringError. - # - # +profile+ is an optional string which will be added to any exception that - # is raised (it does not affect behavior). - def check_prohibited!(string, - *tables, - bidi: false, - unassigned: "A.1", - stored: false, - profile: nil) - tables = Tables::TITLES.keys.grep(/^C/) if tables.empty? - tables |= [unassigned] if stored - tables |= %w[C.8] if bidi - table = tables.find {|t| - case t - when String then Tables::REGEXPS.fetch(t).match?(string) - when Regexp then t.match?(string) - else raise ArgumentError, "only table names and regexps can be checked" - end - } - if table - raise ProhibitedCodepoint.new( - table, string: string, profile: profile - ) - end - check_bidi!(string, profile: profile) if bidi - end - - # Checks that +string+ obeys all of the "Bidirectional Characters" - # requirements in RFC-3454, §6: - # - # * The characters in \StringPrep\[\"C.8\"] MUST be prohibited - # * If a string contains any RandALCat character, the string MUST NOT - # contain any LCat character. - # * If a string contains any RandALCat character, a RandALCat - # character MUST be the first character of the string, and a - # RandALCat character MUST be the last character of the string. - # - # This is usually combined with #check_prohibited!, so table "C.8" is only - # checked when c_8: true. - # - # Raises either ProhibitedCodepoint or BidiStringError unless all - # requirements are met. +profile+ is an optional string which will be - # added to any exception that is raised (it does not affect behavior). - def check_bidi!(string, c_8: false, profile: nil) - check_prohibited!(string, "C.8", profile: profile) if c_8 - if Tables::BIDI_FAILS_REQ2.match?(string) - raise BidiStringError.new( - Tables::BIDI_DESC_REQ2, string: string, profile: profile, - ) - elsif Tables::BIDI_FAILS_REQ3.match?(string) - raise BidiStringError.new( - Tables::BIDI_DESC_REQ3, string: string, profile: profile, - ) - end - end - - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/lib/net/imap.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/lib/net/imap.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,2970 +0,0 @@ -# frozen_string_literal: true -# -# = net/imap.rb -# -# Copyright (C) 2000 Shugo Maeda -# -# This library is distributed under the terms of the Ruby license. -# You can freely distribute/modify this library. -# -# Documentation: Shugo Maeda, with RDoc conversion and overview by William -# Webber. -# -# See Net::IMAP for documentation. -# - -require "socket" -require "monitor" -require 'net/protocol' -begin - require "openssl" -rescue LoadError -end - -module Net - - # Net::IMAP implements Internet Message Access Protocol (\IMAP) client - # functionality. The protocol is described - # in {IMAP4rev1 [RFC3501]}[https://tools.ietf.org/html/rfc3501] - # and {IMAP4rev2 [RFC9051]}[https://tools.ietf.org/html/rfc9051]. - # - # == \IMAP Overview - # - # An \IMAP client connects to a server, and then authenticates - # itself using either #authenticate or #login. Having - # authenticated itself, there is a range of commands - # available to it. Most work with mailboxes, which may be - # arranged in an hierarchical namespace, and each of which - # contains zero or more messages. How this is implemented on - # the server is implementation-dependent; on a UNIX server, it - # will frequently be implemented as files in mailbox format - # within a hierarchy of directories. - # - # To work on the messages within a mailbox, the client must - # first select that mailbox, using either #select or #examine - # (for read-only access). Once the client has successfully - # selected a mailbox, they enter the "_selected_" state, and that - # mailbox becomes the _current_ mailbox, on which mail-item - # related commands implicitly operate. - # - # === Sequence numbers and UIDs - # - # Messages have two sorts of identifiers: message sequence - # numbers and UIDs. - # - # Message sequence numbers number messages within a mailbox - # from 1 up to the number of items in the mailbox. If a new - # message arrives during a session, it receives a sequence - # number equal to the new size of the mailbox. If messages - # are expunged from the mailbox, remaining messages have their - # sequence numbers "shuffled down" to fill the gaps. - # - # To avoid sequence number race conditions, servers must not expunge messages - # when no command is in progress, nor when responding to #fetch, #store, or - # #search. Expunges _may_ be sent during any other command, including - # #uid_fetch, #uid_store, and #uid_search. The #noop and #idle commands are - # both useful for this side-effect: they allow the server to send all mailbox - # updates, including expunges. - # - # UIDs, on the other hand, are permanently guaranteed not to - # identify another message within the same mailbox, even if - # the existing message is deleted. UIDs are required to - # be assigned in ascending (but not necessarily sequential) - # order within a mailbox; this means that if a non-IMAP client - # rearranges the order of mail items within a mailbox, the - # UIDs have to be reassigned. An \IMAP client thus cannot - # rearrange message orders. - # - # === Examples of Usage - # - # ==== List sender and subject of all recent messages in the default mailbox - # - # imap = Net::IMAP.new('mail.example.com') - # imap.authenticate('PLAIN', 'joe_user', 'joes_password') - # imap.examine('INBOX') - # imap.search(["RECENT"]).each do |message_id| - # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] - # puts "#{envelope.from[0].name}: \t#{envelope.subject}" - # end - # - # ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03" - # - # imap = Net::IMAP.new('mail.example.com') - # imap.authenticate('PLAIN', 'joe_user', 'joes_password') - # imap.select('Mail/sent-mail') - # if not imap.list('Mail/', 'sent-apr03') - # imap.create('Mail/sent-apr03') - # end - # imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id| - # imap.copy(message_id, "Mail/sent-apr03") - # imap.store(message_id, "+FLAGS", [:Deleted]) - # end - # imap.expunge - # - # == Capabilities - # - # Most Net::IMAP methods do not _currently_ modify their behaviour according - # to the server's advertised #capabilities. Users of this class must check - # that the server is capable of extension commands or command arguments before - # sending them. Special care should be taken to follow the #capabilities - # requirements for #starttls, #login, and #authenticate. - # - # See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover - # server capabilities. For relevant capability requirements, see the - # documentation on each \IMAP command. - # - # imap = Net::IMAP.new("mail.example.com") - # imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server" - # imap.capable?(:starttls) or raise "Cannot start TLS" - # imap.starttls - # - # if imap.auth_capable?("PLAIN") - # imap.authenticate "PLAIN", username, password - # elsif !imap.capability?("LOGINDISABLED") - # imap.login username, password - # else - # raise "No acceptable authentication mechanisms" - # end - # - # # Support for "UTF8=ACCEPT" implies support for "ENABLE" - # imap.enable :utf8 if imap.capable?("UTF8=ACCEPT") - # - # namespaces = imap.namespace if imap.capable?(:namespace) - # mbox_prefix = namespaces&.personal&.first&.prefix || "" - # mbox_delim = namespaces&.personal&.first&.delim || "/" - # mbox_path = prefix + %w[path to my mailbox].join(delim) - # imap.create mbox_path - # - # === Basic IMAP4rev1 capabilities - # - # IMAP4rev1 servers must advertise +IMAP4rev1+ in their capabilities list. - # IMAP4rev1 servers must _implement_ the +STARTTLS+, AUTH=PLAIN, - # and +LOGINDISABLED+ capabilities. See #starttls, #login, and #authenticate - # for the implications of these capabilities. - # - # === Caching +CAPABILITY+ responses - # - # Net::IMAP automatically stores and discards capability data according to the - # the requirements and recommendations in - # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1], - # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and - # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1]. - # Use #capable?, #auth_capable?, or #capabilities to use this cache and avoid - # sending the #capability command unnecessarily. - # - # The server may advertise its initial capabilities using the +CAPABILITY+ - # ResponseCode in a +PREAUTH+ or +OK+ #greeting. When TLS has started - # (#starttls) and after authentication (#login or #authenticate), the server's - # capabilities may change and cached capabilities are discarded. The server - # may send updated capabilities with an +OK+ TaggedResponse to #login or - # #authenticate, and these will be cached by Net::IMAP. But the - # TaggedResponse to #starttls MUST be ignored--it is sent before TLS starts - # and is unprotected. - # - # When storing capability values to variables, be careful that they are - # discarded or reset appropriately, especially following #starttls. - # - # === Using IMAP4rev1 extensions - # - # See the {IANA IMAP4 capabilities - # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list of - # all standard capabilities, and their reference RFCs. - # - # IMAP4rev1 servers must not activate behavior that is incompatible with the - # base specification until an explicit client action invokes a capability, - # e.g. sending a command or command argument specific to that capability. - # Servers may send data with backward compatible behavior, such as response - # codes or mailbox attributes, at any time without client action. - # - # Invoking capabilities which are unknown to Net::IMAP may cause unexpected - # behavior and errors. For example, ResponseParseError is raised when - # unknown response syntax is received. Invoking commands or command - # parameters that are unsupported by the server may raise NoResponseError, - # BadResponseError, or cause other unexpected behavior. - # - # Some capabilities must be explicitly activated using the #enable command. - # See #enable for details. - # - # == Thread Safety - # - # Net::IMAP supports concurrent threads. For example, - # - # imap = Net::IMAP.new("imap.foo.net", "imap2") - # imap.authenticate("scram-md5", "bar", "password") - # imap.select("inbox") - # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") } - # search_result = imap.search(["BODY", "hello"]) - # fetch_result = fetch_thread.value - # imap.disconnect - # - # This script invokes the FETCH command and the SEARCH command concurrently. - # - # == Errors - # - # An \IMAP server can send three different types of responses to indicate - # failure: - # - # NO:: the attempted command could not be successfully completed. For - # instance, the username/password used for logging in are incorrect; - # the selected mailbox does not exist; etc. - # - # BAD:: the request from the client does not follow the server's - # understanding of the \IMAP protocol. This includes attempting - # commands from the wrong client state; for instance, attempting - # to perform a SEARCH command without having SELECTed a current - # mailbox. It can also signal an internal server - # failure (such as a disk crash) has occurred. - # - # BYE:: the server is saying goodbye. This can be part of a normal - # logout sequence, and can be used as part of a login sequence - # to indicate that the server is (for some reason) unwilling - # to accept your connection. As a response to any other command, - # it indicates either that the server is shutting down, or that - # the server is timing out the client connection due to inactivity. - # - # These three error response are represented by the errors - # Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, and - # Net::IMAP::ByeResponseError, all of which are subclasses of - # Net::IMAP::ResponseError. Essentially, all methods that involve - # sending a request to the server can generate one of these errors. - # Only the most pertinent instances have been documented below. - # - # Because the IMAP class uses Sockets for communication, its methods - # are also susceptible to the various errors that can occur when - # working with sockets. These are generally represented as - # Errno errors. For instance, any method that involves sending a - # request to the server and/or receiving a response from it could - # raise an Errno::EPIPE error if the network connection unexpectedly - # goes down. See the socket(7), ip(7), tcp(7), socket(2), connect(2), - # and associated man pages. - # - # Finally, a Net::IMAP::DataFormatError is thrown if low-level data - # is found to be in an incorrect format (for instance, when converting - # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is - # thrown if a server response is non-parseable. - # - # == What's here? - # - # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods] - # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities] - # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses] - # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands] - # * {for any state}[rdoc-ref:Net::IMAP@Any+state] - # * {for the "not authenticated" state}[rdoc-ref:Net::IMAP@Not+Authenticated+state] - # * {for the "authenticated" state}[rdoc-ref:Net::IMAP@Authenticated+state] - # * {for the "selected" state}[rdoc-ref:Net::IMAP@Selected+state] - # * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state] - # * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support] - # - # === Connection control methods - # - # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and - # waits for a successful server greeting before the method returns. - # - #starttls: Asks the server to upgrade a clear-text connection to use TLS. - # - #logout: Tells the server to end the session. Enters the "_logout_" state. - # - #disconnect: Disconnects the connection (without sending #logout first). - # - #disconnected?: True if the connection has been closed. - # - # === Server capabilities - # - # - #capable?: Returns whether the server supports a given capability. - # - #capabilities: Returns the server's capabilities as an array of strings. - # - #auth_capable?: Returns whether the server advertises support for a given - # SASL mechanism, for use with #authenticate. - # - #auth_mechanisms: Returns the #authenticate SASL mechanisms which - # the server claims to support as an array of strings. - # - #clear_cached_capabilities: Clears cached capabilities. - # - # The capabilities cache is automatically cleared after completing - # #starttls, #login, or #authenticate. - # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities. - # - # In general, #capable? should be used rather than explicitly sending a - # +CAPABILITY+ command to the server. - # - # === Handling server responses - # - # - #greeting: The server's initial untagged response, which can indicate a - # pre-authenticated connection. - # - #responses: Yields unhandled UntaggedResponse#data and non-+nil+ - # ResponseCode#data. - # - #clear_responses: Deletes unhandled data from #responses and returns it. - # - #add_response_handler: Add a block to be called inside the receiver thread - # with every server response. - # - #response_handlers: Returns the list of response handlers. - # - #remove_response_handler: Remove a previously added response handler. - # - # === Core \IMAP commands - # - # The following commands are defined either by - # the [IMAP4rev1[https://tools.ietf.org/html/rfc3501]] base specification, or - # by one of the following extensions: - # [IDLE[https://tools.ietf.org/html/rfc2177]], - # [NAMESPACE[https://tools.ietf.org/html/rfc2342]], - # [UNSELECT[https://tools.ietf.org/html/rfc3691]], - # [ENABLE[https://tools.ietf.org/html/rfc5161]], - # [MOVE[https://tools.ietf.org/html/rfc6851]]. - # These extensions are widely supported by modern IMAP4rev1 servers and have - # all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]]. - # *NOTE:* Net::IMAP doesn't support IMAP4rev2 yet. - # - # ==== Any state - # - # - #capability: Returns the server's capabilities as an array of strings. - # - # In general, #capable? should be used rather than explicitly sending a - # +CAPABILITY+ command to the server. - # - #noop: Allows the server to send unsolicited untagged #responses. - # - #logout: Tells the server to end the session. Enters the "_logout_" state. - # - # ==== Not Authenticated state - # - # In addition to the commands for any state, the following commands are valid - # in the "not authenticated" state: - # - # - #starttls: Upgrades a clear-text connection to use TLS. - # - # Requires the +STARTTLS+ capability. - # - #authenticate: Identifies the client to the server using the given - # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] - # and credentials. Enters the "_authenticated_" state. - # - # The server should list "AUTH=#{mechanism}" capabilities for - # supported mechanisms. - # - #login: Identifies the client to the server using a plain text password. - # Using #authenticate is generally preferred. Enters the "_authenticated_" - # state. - # - # The +LOGINDISABLED+ capability must NOT be listed. - # - # ==== Authenticated state - # - # In addition to the commands for any state, the following commands are valid - # in the "_authenticated_" state: - # - # - #enable: Enables backwards incompatible server extensions. - # Requires the +ENABLE+ or +IMAP4rev2+ capability. - # - #select: Open a mailbox and enter the "_selected_" state. - # - #examine: Open a mailbox read-only, and enter the "_selected_" state. - # - #create: Creates a new mailbox. - # - #delete: Permanently remove a mailbox. - # - #rename: Change the name of a mailbox. - # - #subscribe: Adds a mailbox to the "subscribed" set. - # - #unsubscribe: Removes a mailbox from the "subscribed" set. - # - #list: Returns names and attributes of mailboxes matching a given pattern. - # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters. - # Requires the +NAMESPACE+ or +IMAP4rev2+ capability. - # - #status: Returns mailbox information, e.g. message count, unseen message - # count, +UIDVALIDITY+ and +UIDNEXT+. - # - #append: Appends a message to the end of a mailbox. - # - #idle: Allows the server to send updates to the client, without the client - # needing to poll using #noop. - # Requires the +IDLE+ or +IMAP4rev2+ capability. - # - *Obsolete* #lsub: Replaced by LIST-EXTENDED and removed from - # +IMAP4rev2+. Lists mailboxes in the "subscribed" set. - # - # *Note:* Net::IMAP hasn't implemented LIST-EXTENDED yet. - # - # ==== Selected state - # - # In addition to the commands for any state and the "_authenticated_" - # commands, the following commands are valid in the "_selected_" state: - # - # - #close: Closes the mailbox and returns to the "_authenticated_" state, - # expunging deleted messages, unless the mailbox was opened as read-only. - # - #unselect: Closes the mailbox and returns to the "_authenticated_" state, - # without expunging any messages. - # Requires the +UNSELECT+ or +IMAP4rev2+ capability. - # - #expunge: Permanently removes messages which have the Deleted flag set. - # - #uid_expunge: Restricts expunge to only remove the specified UIDs. - # Requires the +UIDPLUS+ or +IMAP4rev2+ capability. - # - #search, #uid_search: Returns sequence numbers or UIDs of messages that - # match the given searching criteria. - # - #fetch, #uid_fetch: Returns data associated with a set of messages, - # specified by sequence number or UID. - # - #store, #uid_store: Alters a message's flags. - # - #copy, #uid_copy: Copies the specified messages to the end of the - # specified destination mailbox. - # - #move, #uid_move: Moves the specified messages to the end of the - # specified destination mailbox, expunging them from the current mailbox. - # Requires the +MOVE+ or +IMAP4rev2+ capability. - # - #check: *Obsolete:* removed from +IMAP4rev2+. - # Can be replaced with #noop or #idle. - # - # ==== Logout state - # - # No \IMAP commands are valid in the "_logout_" state. If the socket is still - # open, Net::IMAP will close it after receiving server confirmation. - # Exceptions will be raised by \IMAP commands that have already started and - # are waiting for a response, as well as any that are called after logout. - # - # === \IMAP extension support - # - # ==== RFC9051: +IMAP4rev2+ - # - # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported - # yet, Net::IMAP supports several extensions that have been folded into it: - # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, +UNSELECT+, - # STATUS=SIZE, and the fetch side of +BINARY+. - # Commands for these extensions are listed with the {Core IMAP - # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. - # - # >>> - # The following are folded into +IMAP4rev2+ but are currently - # unsupported or incompletely supported by Net::IMAP: RFC4466 - # extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+, - # +LITERAL-+, and +SPECIAL-USE+. - # - # ==== RFC2087: +QUOTA+ - # - #getquota: returns the resource usage and limits for a quota root - # - #getquotaroot: returns the list of quota roots for a mailbox, as well as - # their resource usage and limits. - # - #setquota: sets the resource limits for a given quota root. - # - # ==== RFC2177: +IDLE+ - # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included - # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. - # - #idle: Allows the server to send updates to the client, without the client - # needing to poll using #noop. - # - # ==== RFC2342: +NAMESPACE+ - # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included - # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. - # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters. - # - # ==== RFC2971: +ID+ - # - #id: exchanges client and server implementation information. - # - # ==== RFC3516: +BINARY+ - # The fetch side of +BINARY+ has been folded into - # IMAP4rev2[https://tools.ietf.org/html/rfc9051]. - # - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and - # +BINARY.SIZE+ items. See FetchData#binary and FetchData#binary_size. - # - # >>> - # *NOTE:* The binary extension the #append command is _not_ supported yet. - # - # ==== RFC3691: +UNSELECT+ - # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included - # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. - # - #unselect: Closes the mailbox and returns to the "_authenticated_" state, - # without expunging any messages. - # - # ==== RFC4314: +ACL+ - # - #getacl: lists the authenticated user's access rights to a mailbox. - # - #setacl: sets the access rights for a user on a mailbox - # >>> - # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet. - # - # ==== RFC4315: +UIDPLUS+ - # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included - # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. - # - #uid_expunge: Restricts #expunge to only remove the specified UIDs. - # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode - # - Updates #append with the +APPENDUID+ ResponseCode - # - Updates #copy, #move with the +COPYUID+ ResponseCode - # - # ==== RFC4959: +SASL-IR+ - # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]. - # - Updates #authenticate with the option to send an initial response. - # - # ==== RFC5161: +ENABLE+ - # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included - # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. - # - #enable: Enables backwards incompatible server extensions. - # - # ==== RFC5256: +SORT+ - # - #sort, #uid_sort: An alternate version of #search or #uid_search which - # sorts the results by specified keys. - # ==== RFC5256: +THREAD+ - # - #thread, #uid_thread: An alternate version of #search or #uid_search, - # which arranges the results into ordered groups or threads according to a - # chosen algorithm. - # - # ==== +X-GM-EXT-1+ - # +X-GM-EXT-1+ is a non-standard Gmail extension. See {Google's - # documentation}[https://developers.google.com/gmail/imap/imap-extensions]. - # - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique - # message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels). - # - Updates #search with the +X-GM-RAW+ search attribute. - # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses. - # - # *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and - # +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10). - # - # ==== RFC6851: +MOVE+ - # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included - # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. - # - #move, #uid_move: Moves the specified messages to the end of the - # specified destination mailbox, expunging them from the current mailbox. - # - # ==== RFC6855: UTF8=ACCEPT, UTF8=ONLY - # - # - See #enable for information about support for UTF-8 string encoding. - # - # ==== RFC7162: +CONDSTORE+ - # - # - Updates #enable with +CONDSTORE+ parameter. +CONDSTORE+ will also be - # enabled by using any of the extension's command parameters, listed below. - # - Updates #status with the +HIGHESTMODSEQ+ status attribute. - # - Updates #select and #examine with the +condstore+ modifier, and adds - # either a +HIGHESTMODSEQ+ or +NOMODSEQ+ ResponseCode to the responses. - # - Updates #search, #uid_search, #sort, and #uid_sort with the +MODSEQ+ - # search criterion, and adds SearchResult#modseq to the search response. - # - Updates #thread and #uid_thread with the +MODSEQ+ search criterion - # (but thread responses are unchanged). - # - Updates #fetch and #uid_fetch with the +changedsince+ modifier and - # +MODSEQ+ FetchData attribute. - # - Updates #store and #uid_store with the +unchangedsince+ modifier and adds - # the +MODIFIED+ ResponseCode to the tagged response. - # - # ==== RFC8438: STATUS=SIZE - # - Updates #status with the +SIZE+ status attribute. - # - # ==== RFC8474: +OBJECTID+ - # - Adds +MAILBOXID+ ResponseCode to #create tagged response. - # - Adds +MAILBOXID+ ResponseCode to #select and #examine untagged response. - # - Updates #fetch and #uid_fetch with the +EMAILID+ and +THREADID+ items. - # See FetchData#emailid and FetchData#emailid. - # - Updates #status with support for the +MAILBOXID+ status attribute. - # - # == References - # - # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]:: - # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1", - # RFC 3501, DOI 10.17487/RFC3501, March 2003, - # . - # - # [IMAP-ABNF-EXT[https://www.rfc-editor.org/rfc/rfc4466.html]]:: - # Melnikov, A. and C. Daboo, "Collected Extensions to IMAP4 ABNF", - # RFC 4466, DOI 10.17487/RFC4466, April 2006, - # . - # - # Note: Net::IMAP cannot parse the entire RFC4466 grammar yet. - # - # [{IMAP4rev2}[https://www.rfc-editor.org/rfc/rfc9051.html]]:: - # Melnikov, A., Ed., and B. Leiba, Ed., "Internet Message Access Protocol - # (\IMAP) - Version 4rev2", RFC 9051, DOI 10.17487/RFC9051, August 2021, - # . - # - # Note: Net::IMAP is not fully compatible with IMAP4rev2 yet. - # - # [IMAP-IMPLEMENTATION[https://www.rfc-editor.org/info/rfc2683]]:: - # Leiba, B., "IMAP4 Implementation Recommendations", - # RFC 2683, DOI 10.17487/RFC2683, September 1999, - # . - # - # [IMAP-MULTIACCESS[https://www.rfc-editor.org/info/rfc2180]]:: - # Gahrns, M., "IMAP4 Multi-Accessed Mailbox Practice", RFC 2180, DOI - # 10.17487/RFC2180, July 1997, . - # - # [UTF7[https://tools.ietf.org/html/rfc2152]]:: - # Goldsmith, D. and M. Davis, "UTF-7 A Mail-Safe Transformation Format of - # Unicode", RFC 2152, DOI 10.17487/RFC2152, May 1997, - # . - # - # === Message envelope and body structure - # - # [RFC5322[https://tools.ietf.org/html/rfc5322]]:: - # Resnick, P., Ed., "Internet Message Format", - # RFC 5322, DOI 10.17487/RFC5322, October 2008, - # . - # - # Note: obsoletes - # RFC-2822[https://tools.ietf.org/html/rfc2822] (April 2001) and - # RFC-822[https://tools.ietf.org/html/rfc822] (August 1982). - # - # [CHARSET[https://tools.ietf.org/html/rfc2978]]:: - # Freed, N. and J. Postel, "IANA Charset Registration Procedures", BCP 19, - # RFC 2978, DOI 10.17487/RFC2978, October 2000, - # . - # - # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]:: - # Troost, R., Dorner, S., and K. Moore, Ed., "Communicating Presentation - # Information in Internet Messages: The Content-Disposition Header - # Field", RFC 2183, DOI 10.17487/RFC2183, August 1997, - # . - # - # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]:: - # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions - # (MIME) Part One: Format of Internet Message Bodies", - # RFC 2045, DOI 10.17487/RFC2045, November 1996, - # . - # - # [MIME-IMT[https://tools.ietf.org/html/rfc2046]]:: - # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions - # (MIME) Part Two: Media Types", RFC 2046, DOI 10.17487/RFC2046, - # November 1996, . - # - # [MIME-HDRS[https://tools.ietf.org/html/rfc2047]]:: - # Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part Three: - # Message Header Extensions for Non-ASCII Text", - # RFC 2047, DOI 10.17487/RFC2047, November 1996, - # . - # - # [RFC2231[https://tools.ietf.org/html/rfc2231]]:: - # Freed, N. and K. Moore, "MIME Parameter Value and Encoded Word - # Extensions: Character Sets, Languages, and Continuations", - # RFC 2231, DOI 10.17487/RFC2231, November 1997, - # . - # - # [I18n-HDRS[https://tools.ietf.org/html/rfc6532]]:: - # Yang, A., Steele, S., and N. Freed, "Internationalized Email Headers", - # RFC 6532, DOI 10.17487/RFC6532, February 2012, - # . - # - # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]:: - # Alvestrand, H., "Content Language Headers", - # RFC 3282, DOI 10.17487/RFC3282, May 2002, - # . - # - # [LOCATION[https://www.rfc-editor.org/info/rfc2557]]:: - # Palme, J., Hopmann, A., and N. Shelness, "MIME Encapsulation of - # Aggregate Documents, such as HTML (MHTML)", - # RFC 2557, DOI 10.17487/RFC2557, March 1999, - # . - # - # [MD5[https://tools.ietf.org/html/rfc1864]]:: - # Myers, J. and M. Rose, "The Content-MD5 Header Field", - # RFC 1864, DOI 10.17487/RFC1864, October 1995, - # . - # - # [RFC3503[https://tools.ietf.org/html/rfc3503]]:: - # Melnikov, A., "Message Disposition Notification (MDN) - # profile for Internet Message Access Protocol (IMAP)", - # RFC 3503, DOI 10.17487/RFC3503, March 2003, - # . - # - # === \IMAP Extensions - # - # [QUOTA[https://tools.ietf.org/html/rfc9208]]:: - # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208, - # March 2022, . - # - # Note: obsoletes - # RFC-2087[https://tools.ietf.org/html/rfc2087] (January 1997). - # Net::IMAP does not fully support the RFC9208 updates yet. - # [IDLE[https://tools.ietf.org/html/rfc2177]]:: - # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177, - # June 1997, . - # [NAMESPACE[https://tools.ietf.org/html/rfc2342]]:: - # Gahrns, M. and C. Newman, "IMAP4 Namespace", RFC 2342, - # DOI 10.17487/RFC2342, May 1998, . - # [ID[https://tools.ietf.org/html/rfc2971]]:: - # Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971, - # October 2000, . - # [BINARY[https://tools.ietf.org/html/rfc3516]]:: - # Nerenberg, L., "IMAP4 Binary Content Extension", RFC 3516, - # DOI 10.17487/RFC3516, April 2003, - # . - # [ACL[https://tools.ietf.org/html/rfc4314]]:: - # Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314, - # DOI 10.17487/RFC4314, December 2005, - # . - # [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]]:: - # Crispin, M., "Internet Message Access Protocol (\IMAP) - UIDPLUS - # extension", RFC 4315, DOI 10.17487/RFC4315, December 2005, - # . - # [SORT[https://tools.ietf.org/html/rfc5256]]:: - # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and - # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008, - # . - # [THREAD[https://tools.ietf.org/html/rfc5256]]:: - # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and - # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008, - # . - # [RFC5530[https://www.rfc-editor.org/rfc/rfc5530.html]]:: - # Gulbrandsen, A., "IMAP Response Codes", RFC 5530, DOI 10.17487/RFC5530, - # May 2009, . - # [MOVE[https://tools.ietf.org/html/rfc6851]]:: - # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol - # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013, - # . - # [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]:: - # [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]:: - # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed., - # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013, - # . - # [CONDSTORE[https://tools.ietf.org/html/rfc7162]]:: - # [QRESYNC[https://tools.ietf.org/html/rfc7162]]:: - # Melnikov, A. and D. Cridland, "IMAP Extensions: Quick Flag Changes - # Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization - # (QRESYNC)", RFC 7162, DOI 10.17487/RFC7162, May 2014, - # . - # [OBJECTID[https://tools.ietf.org/html/rfc8474]]:: - # Gondwana, B., Ed., "IMAP Extension for Object Identifiers", - # RFC 8474, DOI 10.17487/RFC8474, September 2018, - # . - # - # === IANA registries - # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities] - # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml] - # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml] - # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml] - # * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml] - # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] - # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]: - # +imap+: tcp/143, +imaps+: tcp/993 - # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]: - # +imap+ - # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml] - # ===== For currently unsupported features: - # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2] - # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml] - # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml] - # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml] - # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml] - # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml] - # - class IMAP < Protocol - VERSION = "0.4.9" - - # Aliases for supported capabilities, to be used with the #enable command. - ENABLE_ALIASES = { - utf8: "UTF8=ACCEPT", - "UTF8=ONLY" => "UTF8=ACCEPT", - }.freeze - - autoload :SASL, File.expand_path("imap/sasl", __dir__) - autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__) - autoload :StringPrep, File.expand_path("imap/stringprep", __dir__) - - include MonitorMixin - if defined?(OpenSSL::SSL) - include OpenSSL - include SSL - end - - # Returns the debug mode. - def self.debug - return @@debug - end - - # Sets the debug mode. - def self.debug=(val) - return @@debug = val - end - - # The default port for IMAP connections, port 143 - def self.default_port - return PORT - end - - # The default port for IMAPS connections, port 993 - def self.default_tls_port - return SSL_PORT - end - - class << self - alias default_imap_port default_port - alias default_imaps_port default_tls_port - alias default_ssl_port default_tls_port - end - - # Returns the initial greeting the server, an UntaggedResponse. - attr_reader :greeting - - # Seconds to wait until a connection is opened. - # If the IMAP object cannot open a connection within this time, - # it raises a Net::OpenTimeout exception. The default value is 30 seconds. - attr_reader :open_timeout - - # Seconds to wait until an IDLE response is received. - attr_reader :idle_response_timeout - - # The hostname this client connected to - attr_reader :host - - # The port this client connected to - attr_reader :port - - # Returns the - # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] - # used by the SSLSocket when TLS is attempted, even when the TLS handshake - # is unsuccessful. The context object will be frozen. - # - # Returns +nil+ for a plaintext connection. - attr_reader :ssl_ctx - - # Returns the parameters that were sent to #ssl_ctx - # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params] - # when the connection tries to use TLS (even when unsuccessful). - # - # Returns +false+ for a plaintext connection. - attr_reader :ssl_ctx_params - - # Creates a new Net::IMAP object and connects it to the specified - # +host+. - # - # ==== Options - # - # Accepts the following options: - # - # [port] - # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise. - # - # [ssl] - # If +true+, the connection will use TLS with the default params set by - # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]. - # If +ssl+ is a hash, it's passed to - # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]; - # the keys are names of attribute assignment methods on - # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]. - # - # [open_timeout] - # Seconds to wait until a connection is opened - # [idle_response_timeout] - # Seconds to wait until an IDLE response is received - # - # See DeprecatedClientOptions.new for deprecated arguments. - # - # ==== Examples - # - # Connect to cleartext port 143 at mail.example.com and recieve the server greeting: - # imap = Net::IMAP.new('mail.example.com', ssl: false) # => # - # imap.port => 143 - # imap.tls_verified? => false - # imap.greeting => name: ("OK" | "PREAUTH") => status - # status # => "OK" - # # The client is connected in the "Not Authenticated" state. - # - # Connect with TLS to port 993 - # imap = Net::IMAP.new('mail.example.com', ssl: true) # => # - # imap.port => 993 - # imap.tls_verified? => true - # imap.greeting => name: (/OK/i | /PREAUTH/i) => status - # case status - # in /OK/i - # # The client is connected in the "Not Authenticated" state. - # imap.authenticate("PLAIN", "joe_user", "joes_password") - # in /PREAUTH/i - # # The client is connected in the "Authenticated" state. - # end - # - # Connect with prior authentication, for example using an SSL certificate: - # ssl_ctx_params = { - # cert: OpenSSL::X509::Certificate.new(File.read("client.crt")), - # key: OpenSSL::PKey::EC.new(File.read('client.key')), - # extra_chain_cert: [ - # OpenSSL::X509::Certificate.new(File.read("intermediate.crt")), - # ], - # } - # imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params) - # imap.port => 993 - # imap.tls_verified? => true - # imap.greeting => name: "PREAUTH" - # # The client is connected in the "Authenticated" state. - # - # ==== Exceptions - # - # The most common errors are: - # - # [Errno::ECONNREFUSED] - # Connection refused by +host+ or an intervening firewall. - # [Errno::ETIMEDOUT] - # Connection timed out (possibly due to packets being dropped by an - # intervening firewall). - # [Errno::ENETUNREACH] - # There is no route to that network. - # [SocketError] - # Hostname not known or other socket error. - # [Net::IMAP::ByeResponseError] - # Connected to the host successfully, but it immediately said goodbye. - # - def initialize(host, port: nil, ssl: nil, - open_timeout: 30, idle_response_timeout: 5) - super() - # Config options - @host = host - @port = port || (ssl ? SSL_PORT : PORT) - @open_timeout = Integer(open_timeout) - @idle_response_timeout = Integer(idle_response_timeout) - @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl) - - # Basic Client State - @utf8_strings = false - @debug_output_bol = true - @exception = nil - @greeting = nil - @capabilities = nil - - # Client Protocol Reciever - @parser = ResponseParser.new - @responses = Hash.new {|h, k| h[k] = [] } - @response_handlers = [] - @receiver_thread = nil - @receiver_thread_exception = nil - @receiver_thread_terminating = false - - # Client Protocol Sender (including state for currently running commands) - @tag_prefix = "RUBY" - @tagno = 0 - @tagged_responses = {} - @tagged_response_arrival = new_cond - @continued_command_tag = nil - @continuation_request_arrival = new_cond - @continuation_request_exception = nil - @idle_done_cond = nil - @logout_command_tag = nil - - # Connection - @tls_verified = false - @sock = tcp_socket(@host, @port) - start_tls_session if ssl_ctx - start_imap_connection - - # DEPRECATED: to remove in next version - @client_thread = Thread.current - end - - # Returns true after the TLS negotiation has completed and the remote - # hostname has been verified. Returns false when TLS has been established - # but peer verification was disabled. - def tls_verified?; @tls_verified end - - def client_thread # :nodoc: - warn "Net::IMAP#client_thread is deprecated and will be removed soon." - @client_thread - end - - # Disconnects from the server. - # - # Related: #logout, #logout! - def disconnect - return if disconnected? - begin - begin - # try to call SSL::SSLSocket#io. - @sock.io.shutdown - rescue NoMethodError - # @sock is not an SSL::SSLSocket. - @sock.shutdown - end - rescue Errno::ENOTCONN - # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms. - rescue Exception => e - @receiver_thread.raise(e) - end - @receiver_thread.join - synchronize do - @sock.close - end - raise e if e - end - - # Returns true if disconnected from the server. - # - # Related: #logout, #disconnect - def disconnected? - return @sock.closed? - end - - # Returns whether the server supports a given +capability+. When available, - # cached #capabilities are used without sending a new #capability command to - # the server. - # - # *NOTE:* Most Net::IMAP methods do not _currently_ modify their - # behaviour according to the server's advertised #capabilities. - # - # See Net::IMAP@Capabilities for more about \IMAP capabilities. - # - # Related: #auth_capable?, #capabilities, #capability, #enable - def capable?(capability) capabilities.include? capability.to_s.upcase end - alias capability? capable? - - # Returns the server capabilities. When available, cached capabilities are - # used without sending a new #capability command to the server. - # - # To ensure a case-insensitive comparison, #capable? can be used instead. - # - # *NOTE:* Most Net::IMAP methods do not _currently_ modify their - # behaviour according to the server's advertised #capabilities. - # - # See Net::IMAP@Capabilities for more about \IMAP capabilities. - # - # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable - def capabilities - @capabilities || capability - end - - # Returns the #authenticate mechanisms that the server claims to support. - # These are derived from the #capabilities with an AUTH= prefix. - # - # This may be different when the connection is cleartext or using TLS. Most - # servers will drop all AUTH= mechanisms from #capabilities after - # the connection has authenticated. - # - # imap = Net::IMAP.new(hostname, ssl: false) - # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"] - # imap.auth_mechanisms # => [] - # - # imap.starttls - # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2", - # # "AUTH=OAUTHBEARER"] - # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"] - # - # imap.authenticate("XOAUTH2", username, oauth2_access_token) - # imap.auth_mechanisms # => [] - # - # Related: #authenticate, #auth_capable?, #capabilities - def auth_mechanisms - capabilities - .grep(/\AAUTH=/i) - .map { _1.delete_prefix("AUTH=") } - end - - # Returns whether the server supports a given SASL +mechanism+ for use with - # the #authenticate command. The +mechanism+ is supported when - # #capabilities includes "AUTH=#{mechanism.to_s.upcase}". When - # available, cached capabilities are used without sending a new #capability - # command to the server. - # - # imap.capable? "AUTH=PLAIN" # => true - # imap.auth_capable? "PLAIN" # => true - # imap.auth_capable? "blurdybloop" # => false - # - # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities - def auth_capable?(mechanism) - capable? "AUTH=#{mechanism}" - end - - # Returns whether capabilities have been cached. When true, #capable? and - # #capabilities don't require sending a #capability command to the server. - # - # See Net::IMAP@Capabilities for more about \IMAP capabilities. - # - # Related: #capable?, #capability, #clear_cached_capabilities - def capabilities_cached? - !!@capabilities - end - - # Clears capabilities that have been remembered by the Net::IMAP client. - # This forces a #capability command to be sent the next time a #capabilities - # query method is called. - # - # Net::IMAP automatically discards its cached capabilities when they can - # change. Explicitly calling this _should_ be unnecessary for well-behaved - # servers. - # - # Related: #capable?, #capability, #capabilities_cached? - def clear_cached_capabilities - synchronize do - clear_responses("CAPABILITY") - @capabilities = nil - end - end - - # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1] - # and returns an array of capabilities that are supported by the server. - # The result is stored for use by #capable? and #capabilities. - # - # *NOTE:* Most Net::IMAP methods do not _currently_ modify their - # behaviour according to the server's advertised #capabilities. - # - # Net::IMAP automatically stores and discards capability data according to - # the requirements and recommendations in - # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1], - # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and - # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1]. - # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid - # sending the #capability command unnecessarily. - # - # See Net::IMAP@Capabilities for more about \IMAP capabilities. - # - # Related: #capable?, #auth_capable?, #capability, #enable - def capability - synchronize do - send_command("CAPABILITY") - @capabilities = clear_responses("CAPABILITY").last.freeze - end - end - - # Sends an {ID command [RFC2971 §3.1]}[https://www.rfc-editor.org/rfc/rfc2971#section-3.1] - # and returns a hash of the server's response, or nil if the server does not - # identify itself. - # - # Note that the user should first check if the server supports the ID - # capability. For example: - # - # if capable?(:ID) - # id = imap.id( - # name: "my IMAP client (ruby)", - # version: MyIMAP::VERSION, - # "support-url": "mailto:bugs@example.com", - # os: RbConfig::CONFIG["host_os"], - # ) - # end - # - # See [ID[https://tools.ietf.org/html/rfc2971]] for field definitions. - # - # ===== Capabilities - # - # The server's capabilities must include +ID+ - # [RFC2971[https://tools.ietf.org/html/rfc2971]]. - def id(client_id=nil) - synchronize do - send_command("ID", ClientID.new(client_id)) - clear_responses("ID").last - end - end - - # Sends a {NOOP command [IMAP4rev1 §6.1.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.2] - # to the server. - # - # This allows the server to send unsolicited untagged EXPUNGE #responses, - # but does not execute any client request. \IMAP servers are permitted to - # send unsolicited untagged responses at any time, except for +EXPUNGE+: - # - # * +EXPUNGE+ can only be sent while a command is in progress. - # * +EXPUNGE+ must _not_ be sent during #fetch, #store, or #search. - # * +EXPUNGE+ may be sent during #uid_fetch, #uid_store, or #uid_search. - # - # Related: #idle, #check - def noop - send_command("NOOP") - end - - # Sends a {LOGOUT command [IMAP4rev1 §6.1.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.3] - # to inform the command to inform the server that the client is done with - # the connection. - # - # Related: #disconnect, #logout! - def logout - send_command("LOGOUT") - end - - # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+, - # calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns - # +nil+ when the client is already disconnected, in contrast to #logout - # which raises an exception. - # - # If #logout raises a StandardError, a warning will be printed but the - # exception will not be re-raised. - # - # This is useful in situations where the connection must be dropped, for - # example for security or after tests. If logout errors need to be handled, - # use #logout and #disconnect instead. - # - # Related: #logout, #disconnect - def logout! - logout unless disconnected? - rescue => ex - warn "%s during logout!: %s" % [ - ex.class, host, port, ex - ] - ensure - disconnect - end - - # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1] - # to start a TLS session. - # - # Any +options+ are forwarded directly to - # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]; - # the keys are names of attribute assignment methods on - # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]. - # - # See DeprecatedClientOptions#starttls for deprecated arguments. - # - # This method returns after TLS negotiation and hostname verification are - # both successful. Any error indicates that the connection has not been - # secured. - # - # *Note:* - # >>> - # Any #response_handlers added before STARTTLS should be aware that the - # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation. - # TLS starts immediately _after_ that response. Any response code sent - # with the response (e.g. CAPABILITY) is insecure and cannot be trusted. - # - # Related: Net::IMAP.new, #login, #authenticate - # - # ===== Capability - # Clients should not call #starttls unless the server advertises the - # +STARTTLS+ capability. - # - # Server capabilities may change after #starttls, #login, and #authenticate. - # Cached #capabilities will be cleared when this method completes. - # - def starttls(**options) - @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options) - send_command("STARTTLS") do |resp| - if resp.kind_of?(TaggedResponse) && resp.name == "OK" - clear_cached_capabilities - clear_responses - start_tls_session - end - end - end - - # :call-seq: - # authenticate(mechanism, *, sasl_ir: true, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp - # - # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2] - # to authenticate the client. If successful, the connection enters the - # "_authenticated_" state. - # - # +mechanism+ is the name of the \SASL authentication mechanism to be used. - # - # +sasl_ir+ allows or disallows sending an "initial response" (see the - # +SASL-IR+ capability, below). - # - # All other arguments are forwarded to the registered SASL authenticator for - # the requested mechanism. The documentation for each individual - # mechanism must be consulted for its specific parameters. - # - # Related: #login, #starttls, #auth_capable?, #auth_mechanisms - # - # ==== Mechanisms - # - # Each mechanism has different properties and requirements. Please consult - # the documentation for the specific mechanisms you are using: - # - # +ANONYMOUS+:: - # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator]. - # - # Allows the user to gain access to public services or resources without - # authenticating or disclosing an identity. - # - # +EXTERNAL+:: - # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator]. - # - # Authenticates using already established credentials, such as a TLS - # certificate or IPsec. - # - # +OAUTHBEARER+:: - # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator]. - # - # Login using an OAuth2 Bearer token. This is the standard mechanism - # for using OAuth2 with \SASL, but it is not yet deployed as widely as - # +XOAUTH2+. - # - # +PLAIN+:: - # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator]. - # - # Login using clear-text username and password. - # - # +SCRAM-SHA-1+:: - # +SCRAM-SHA-256+:: - # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator]. - # - # Login by username and password. The password is not sent to the - # server but is used in a salted challenge/response exchange. - # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by - # Net::IMAP::SASL. New authenticators can easily be added for any other - # SCRAM-* mechanism if the digest algorithm is supported by - # OpenSSL::Digest. - # - # +XOAUTH2+:: - # See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator]. - # - # Login using a username and an OAuth2 access token. Non-standard and - # obsoleted by +OAUTHBEARER+, but widely supported. - # - # See the {SASL mechanism - # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] - # for a list of all SASL mechanisms and their specifications. To register - # new authenticators, see Authenticators. - # - # ===== Deprecated mechanisms - # - # Obsolete mechanisms should be avoided, but are still available for - # backwards compatibility. See Net::IMAP::SASL@Deprecated+mechanisms. - # Using a deprecated mechanism will print a warning. - # - # ==== Capabilities - # - # "AUTH=#{mechanism}" capabilities indicate server support for - # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support - # before using a particular mechanism. - # - # if imap.auth_capable? "XOAUTH2" - # imap.authenticate "XOAUTH2", username, oauth2_access_token - # elsif imap.auth_capable? "PLAIN" - # imap.authenticate "PLAIN", username, password - # elsif !imap.capability? "LOGINDISABLED" - # imap.login username, password - # else - # raise "No acceptable authentication mechanism is available" - # end - # - # Although servers should list all supported \SASL mechanisms, they may - # allow authentication with an unlisted +mechanism+. - # - # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported - # and the appropriate "AUTH=#{mechanism}" capability is present, - # an "initial response" may be sent as an argument to the +AUTHENTICATE+ - # command, saving a round-trip. The SASL exchange allows for server - # challenges and client responses, but many mechanisms expect the client to - # "respond" first. The initial response will only be sent for - # "client-first" mechanisms. - # - # Server capabilities may change after #starttls, #login, and #authenticate. - # Previously cached #capabilities will be cleared when this method - # completes. If the TaggedResponse to #authenticate includes updated - # capabilities, they will be cached. - def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback) - mechanism = mechanism.to_s.tr("_", "-").upcase - authenticator = SASL.authenticator(mechanism, *creds, **props, &callback) - cmdargs = ["AUTHENTICATE", mechanism] - if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) && - authenticator.respond_to?(:initial_response?) && - authenticator.initial_response? - response = authenticator.process(nil) - cmdargs << (response.empty? ? "=" : [response].pack("m0")) - end - result = send_command_with_continuations(*cmdargs) {|data| - challenge = data.unpack1("m") - response = authenticator.process challenge - [response].pack("m0") - } - if authenticator.respond_to?(:done?) && !authenticator.done? - logout! - raise SASL::AuthenticationIncomplete, result - end - @capabilities = capabilities_from_resp_code result - result - end - - # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3] - # to identify the client and carries the plaintext +password+ authenticating - # this +user+. If successful, the connection enters the "_authenticated_" - # state. - # - # Using #authenticate {should be - # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command] - # over #login. The LOGIN command is not the same as #authenticate with the - # "LOGIN" +mechanism+. - # - # A Net::IMAP::NoResponseError is raised if authentication fails. - # - # Related: #authenticate, #starttls - # - # ===== Capabilities - # - # An IMAP client MUST NOT call #login when the server advertises the - # +LOGINDISABLED+ capability. - # - # if imap.capability? "LOGINDISABLED" - # raise "Remote server has disabled the login command" - # else - # imap.login username, password - # end - # - # Server capabilities may change after #starttls, #login, and #authenticate. - # Cached capabilities _must_ be invalidated after this method completes. - # The TaggedResponse to #login may include updated capabilities in its - # ResponseCode. - # - def login(user, password) - send_command("LOGIN", user, password) - .tap { @capabilities = capabilities_from_resp_code _1 } - end - - # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1] - # to select a +mailbox+ so that messages in the +mailbox+ can be accessed. - # - # After you have selected a mailbox, you may retrieve the number of items in - # that mailbox from imap.responses("EXISTS", &:last), and the - # number of recent messages from imap.responses("RECENT", &:last). - # Note that these values can change if new messages arrive during a session - # or when existing messages are expunged; see #add_response_handler for a - # way to detect these events. - # - # When the +condstore+ keyword argument is true, the server is told to - # enable the extension. If +mailbox+ supports persistence of mod-sequences, - # the +HIGHESTMODSEQ+ ResponseCode will be sent as an untagged response to - # #select and all `FETCH` responses will include FetchData#modseq. - # Otherwise, the +NOMODSEQ+ ResponseCode will be sent. - # - # A Net::IMAP::NoResponseError is raised if the mailbox does not - # exist or is for some reason non-selectable. - # - # Related: #examine - # - # ===== Capabilities - # - # If [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]] is supported, - # the server may return an untagged "NO" response with a "UIDNOTSTICKY" - # response code indicating that the mailstore does not support persistent - # UIDs: - # imap.responses("NO", &:last)&.code&.name == "UIDNOTSTICKY" - # - # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported, - # the +condstore+ keyword parameter may be used. - # imap.select("mbox", condstore: true) - # modseq = imap.responses("HIGHESTMODSEQ", &:last) - def select(mailbox, condstore: false) - args = ["SELECT", mailbox] - args << ["CONDSTORE"] if condstore - synchronize do - @responses.clear - send_command(*args) - end - end - - # Sends a {EXAMINE command [IMAP4rev1 §6.3.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.2] - # to select a +mailbox+ so that messages in the +mailbox+ can be accessed. - # Behaves the same as #select, except that the selected +mailbox+ is - # identified as read-only. - # - # A Net::IMAP::NoResponseError is raised if the mailbox does not - # exist or is for some reason non-examinable. - # - # Related: #select - def examine(mailbox, condstore: false) - args = ["EXAMINE", mailbox] - args << ["CONDSTORE"] if condstore - synchronize do - @responses.clear - send_command(*args) - end - end - - # Sends a {CREATE command [IMAP4rev1 §6.3.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.3] - # to create a new +mailbox+. - # - # A Net::IMAP::NoResponseError is raised if a mailbox with that name - # cannot be created. - # - # Related: #rename, #delete - def create(mailbox) - send_command("CREATE", mailbox) - end - - # Sends a {DELETE command [IMAP4rev1 §6.3.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.4] - # to remove the +mailbox+. - # - # A Net::IMAP::NoResponseError is raised if a mailbox with that name - # cannot be deleted, either because it does not exist or because the - # client does not have permission to delete it. - # - # Related: #create, #rename - def delete(mailbox) - send_command("DELETE", mailbox) - end - - # Sends a {RENAME command [IMAP4rev1 §6.3.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.5] - # to change the name of the +mailbox+ to +newname+. - # - # A Net::IMAP::NoResponseError is raised if a mailbox with the - # name +mailbox+ cannot be renamed to +newname+ for whatever - # reason; for instance, because +mailbox+ does not exist, or - # because there is already a mailbox with the name +newname+. - # - # Related: #create, #delete - def rename(mailbox, newname) - send_command("RENAME", mailbox, newname) - end - - # Sends a {SUBSCRIBE command [IMAP4rev1 §6.3.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.6] - # to add the specified +mailbox+ name to the server's set of "active" or - # "subscribed" mailboxes as returned by #lsub. - # - # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be - # subscribed to; for instance, because it does not exist. - # - # Related: #unsubscribe, #lsub, #list - def subscribe(mailbox) - send_command("SUBSCRIBE", mailbox) - end - - # Sends an {UNSUBSCRIBE command [IMAP4rev1 §6.3.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.7] - # to remove the specified +mailbox+ name from the server's set of "active" - # or "subscribed" mailboxes. - # - # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be - # unsubscribed from; for instance, because the client is not currently - # subscribed to it. - # - # Related: #subscribe, #lsub, #list - def unsubscribe(mailbox) - send_command("UNSUBSCRIBE", mailbox) - end - - # Sends a {LIST command [IMAP4rev1 §6.3.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.8] - # and returns a subset of names from the complete set of all names available - # to the client. +refname+ provides a context (for instance, a base - # directory in a directory-based mailbox hierarchy). +mailbox+ specifies a - # mailbox or (via wildcards) mailboxes under that context. Two wildcards - # may be used in +mailbox+: "*", which matches all characters - # *including* the hierarchy delimiter (for instance, "/" on a UNIX-hosted - # directory-based mailbox hierarchy); and "%", which matches all - # characters *except* the hierarchy delimiter. - # - # If +refname+ is empty, +mailbox+ is used directly to determine - # which mailboxes to match. If +mailbox+ is empty, the root - # name of +refname+ and the hierarchy delimiter are returned. - # - # The return value is an array of MailboxList. - # - # Related: #lsub, MailboxList - # - # ===== For example: - # - # imap.create("foo/bar") - # imap.create("foo/baz") - # p imap.list("", "foo/%") - # #=> [#, \\ - # #, \\ - # #] - # - #-- - # TODO: support LIST-EXTENDED extension [RFC5258]. Needed for IMAP4rev2. - #++ - def list(refname, mailbox) - synchronize do - send_command("LIST", refname, mailbox) - clear_responses("LIST") - end - end - - # Sends a {NAMESPACE command [RFC2342 §5]}[https://www.rfc-editor.org/rfc/rfc2342#section-5] - # and returns the namespaces that are available. The NAMESPACE command - # allows a client to discover the prefixes of namespaces used by a server - # for personal mailboxes, other users' mailboxes, and shared mailboxes. - # - # The return value is a Namespaces object which has +personal+, +other+, and - # +shared+ fields, each an array of Namespace objects. These arrays will be - # empty when the server responds with +nil+. - # - # Many \IMAP servers are configured with the default personal namespaces as - # ("" "/"): no prefix and the "+/+" hierarchy delimiter. In that - # common case, the naive client may not have any trouble naming mailboxes. - # But many servers are configured with the default personal namespace as - # e.g. ("INBOX." "."), placing all personal folders under INBOX, - # with "+.+" as the hierarchy delimiter. If the client does not check for - # this, but naively assumes it can use the same folder names for all - # servers, then folder creation (and listing, moving, etc) can lead to - # errors. - # - # From RFC2342[https://tools.ietf.org/html/rfc2342]: - # >>> - # Although typically a server will support only a single Personal - # Namespace, and a single Other User's Namespace, circumstances exist - # where there MAY be multiples of these, and a client MUST be prepared - # for them. If a client is configured such that it is required to create - # a certain mailbox, there can be circumstances where it is unclear which - # Personal Namespaces it should create the mailbox in. In these - # situations a client SHOULD let the user select which namespaces to - # create the mailbox in. - # - # Related: #list, Namespaces, Namespace - # - # ===== For example: - # - # if capable?("NAMESPACE") - # namespaces = imap.namespace - # if namespace = namespaces.personal.first - # prefix = namespace.prefix # e.g. "" or "INBOX." - # delim = namespace.delim # e.g. "/" or "." - # # personal folders should use the prefix and delimiter - # imap.create(prefix + "foo") - # imap.create(prefix + "bar") - # imap.create(prefix + %w[path to my folder].join(delim)) - # end - # end - # - # ===== Capabilities - # - # The server's capabilities must include +NAMESPACE+ - # [RFC2342[https://tools.ietf.org/html/rfc2342]]. - def namespace - synchronize do - send_command("NAMESPACE") - clear_responses("NAMESPACE").last - end - end - - # Sends a XLIST command, and returns a subset of names from - # the complete set of all names available to the client. - # +refname+ provides a context (for instance, a base directory - # in a directory-based mailbox hierarchy). +mailbox+ specifies - # a mailbox or (via wildcards) mailboxes under that context. - # Two wildcards may be used in +mailbox+: '*', which matches - # all characters *including* the hierarchy delimiter (for instance, - # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%', - # which matches all characters *except* the hierarchy delimiter. - # - # If +refname+ is empty, +mailbox+ is used directly to determine - # which mailboxes to match. If +mailbox+ is empty, the root - # name of +refname+ and the hierarchy delimiter are returned. - # - # The XLIST command is like the LIST command except that the flags - # returned refer to the function of the folder/mailbox, e.g. :Sent - # - # The return value is an array of MailboxList objects. For example: - # - # imap.create("foo/bar") - # imap.create("foo/baz") - # p imap.xlist("", "foo/%") - # #=> [#, \\ - # #, \\ - # #] - # - # Related: #list, MailboxList - # - # ===== Capabilities - # - # The server's capabilities must include +XLIST+, - # a deprecated Gmail extension (replaced by +SPECIAL-USE+). - #-- - # TODO: Net::IMAP doesn't yet have full SPECIAL-USE support. Supporting - # servers MAY return SPECIAL-USE attributes, but are not *required* to - # unless the SPECIAL-USE return option is supplied. - #++ - def xlist(refname, mailbox) - synchronize do - send_command("XLIST", refname, mailbox) - clear_responses("XLIST") - end - end - - # Sends a {GETQUOTAROOT command [RFC2087 §4.3]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.3] - # along with the specified +mailbox+. This command is generally available - # to both admin and user. If this mailbox exists, it returns an array - # containing objects of type MailboxQuotaRoot and MailboxQuota. - # - # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota - # - # ===== Capabilities - # - # The server's capabilities must include +QUOTA+ - # [RFC2087[https://tools.ietf.org/html/rfc2087]]. - def getquotaroot(mailbox) - synchronize do - send_command("GETQUOTAROOT", mailbox) - result = [] - result.concat(clear_responses("QUOTAROOT")) - result.concat(clear_responses("QUOTA")) - return result - end - end - - # Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2] - # along with specified +mailbox+. If this mailbox exists, then an array - # containing a MailboxQuota object is returned. This command is generally - # only available to server admin. - # - # Related: #getquotaroot, #setquota, MailboxQuota - # - # ===== Capabilities - # - # The server's capabilities must include +QUOTA+ - # [RFC2087[https://tools.ietf.org/html/rfc2087]]. - def getquota(mailbox) - synchronize do - send_command("GETQUOTA", mailbox) - clear_responses("QUOTA") - end - end - - # Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1] - # along with the specified +mailbox+ and +quota+. If +quota+ is nil, then - # +quota+ will be unset for that mailbox. Typically one needs to be logged - # in as a server admin for this to work. - # - # Related: #getquota, #getquotaroot - # - # ===== Capabilities - # - # The server's capabilities must include +QUOTA+ - # [RFC2087[https://tools.ietf.org/html/rfc2087]]. - def setquota(mailbox, quota) - if quota.nil? - data = '()' - else - data = '(STORAGE ' + quota.to_s + ')' - end - send_command("SETQUOTA", mailbox, RawData.new(data)) - end - - # Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1] - # along with +mailbox+, +user+ and the +rights+ that user is to have on that - # mailbox. If +rights+ is nil, then that user will be stripped of any - # rights to that mailbox. - # - # Related: #getacl - # - # ===== Capabilities - # - # The server's capabilities must include +ACL+ - # [RFC4314[https://tools.ietf.org/html/rfc4314]]. - def setacl(mailbox, user, rights) - if rights.nil? - send_command("SETACL", mailbox, user, "") - else - send_command("SETACL", mailbox, user, rights) - end - end - - # Sends a {GETACL command [RFC4314 §3.3]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.3] - # along with a specified +mailbox+. If this mailbox exists, an array - # containing objects of MailboxACLItem will be returned. - # - # Related: #setacl, MailboxACLItem - # - # ===== Capabilities - # - # The server's capabilities must include +ACL+ - # [RFC4314[https://tools.ietf.org/html/rfc4314]]. - def getacl(mailbox) - synchronize do - send_command("GETACL", mailbox) - clear_responses("ACL").last - end - end - - # Sends a {LSUB command [IMAP4rev1 §6.3.9]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.9] - # and returns a subset of names from the set of names that the user has - # declared as being "active" or "subscribed." +refname+ and +mailbox+ are - # interpreted as for #list. - # - # The return value is an array of MailboxList objects. - # - # Related: #subscribe, #unsubscribe, #list, MailboxList - def lsub(refname, mailbox) - synchronize do - send_command("LSUB", refname, mailbox) - clear_responses("LSUB") - end - end - - # Sends a {STATUS command [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10] - # and returns the status of the indicated +mailbox+. +attr+ is a list of one - # or more attributes whose statuses are to be requested. - # - # The return value is a hash of attributes. Most status attributes return - # integer values, but some return other value types (documented below). - # - # A Net::IMAP::NoResponseError is raised if status values - # for +mailbox+ cannot be returned; for instance, because it - # does not exist. - # - # ===== Supported attributes - # - # +MESSAGES+:: The number of messages in the mailbox. - # - # +UIDNEXT+:: The next unique identifier value of the mailbox. - # - # +UIDVALIDITY+:: The unique identifier validity value of the mailbox. - # - # +UNSEEN+:: The number of messages without the \Seen flag. - # - # +DELETED+:: The number of messages with the \Deleted flag. - # - # +SIZE+:: - # The approximate size of the mailbox---must be greater than or equal to - # the sum of all messages' +RFC822.SIZE+ fetch item values. - # - # +HIGHESTMODSEQ+:: - # The highest mod-sequence value of all messages in the mailbox. See - # +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. - # - # +MAILBOXID+:: - # A server-allocated unique _string_ identifier for the mailbox. See - # +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. - # - # +RECENT+:: - # The number of messages with the \Recent flag. - # _NOTE:_ +RECENT+ was removed from IMAP4rev2. - # - # Unsupported attributes may be requested. The attribute value will be - # either an Integer or an ExtensionData object. - # - # ===== For example: - # - # p imap.status("inbox", ["MESSAGES", "RECENT"]) - # #=> {"RECENT"=>0, "MESSAGES"=>44} - # - # ===== Capabilities - # - # +SIZE+ requires the server's capabilities to include either +IMAP4rev2+ or - # STATUS=SIZE - # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html]. - # - # +DELETED+ requires the server's capabilities to include +IMAP4rev2+. - # - # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+ - # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. - # - # +MAILBOXID+ requires the server's capabilities to include +OBJECTID+ - # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. - def status(mailbox, attr) - synchronize do - send_command("STATUS", mailbox, attr) - clear_responses("STATUS").last&.attr - end - end - - # Sends an {APPEND command [IMAP4rev1 §6.3.11]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.11] - # to append the +message+ to the end of the +mailbox+. The optional +flags+ - # argument is an array of flags initially passed to the new message. The - # optional +date_time+ argument specifies the creation time to assign to the - # new message; it defaults to the current time. - # - # For example: - # - # imap.append("inbox", <\\Deleted - # flag set. - # - # Related: #unselect - def close - send_command("CLOSE") - end - - # Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3] - # {[IMAP4rev2 §6.4.2]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.2] - # to free the session resources for a mailbox and return to the - # "_authenticated_" state. This is the same as #close, except that - # \\Deleted messages are not removed from the mailbox. - # - # Related: #close - # - # ===== Capabilities - # - # The server's capabilities must include +UNSELECT+ - # [RFC3691[https://tools.ietf.org/html/rfc3691]]. - def unselect - send_command("UNSELECT") - end - - # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3] - # Sends a EXPUNGE command to permanently remove from the currently - # selected mailbox all messages that have the \Deleted flag set. - # - # Related: #uid_expunge - def expunge - synchronize do - send_command("EXPUNGE") - clear_responses("EXPUNGE") - end - end - - # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1] - # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9] - # to permanently remove all messages that have both the \\Deleted - # flag set and a UID that is included in +uid_set+. - # - # By using #uid_expunge instead of #expunge when resynchronizing with - # the server, the client can ensure that it does not inadvertantly - # remove any messages that have been marked as \\Deleted by other - # clients between the time that the client was last connected and - # the time the client resynchronizes. - # - # *Note:* - # >>> - # Although the command takes a set of UIDs for its argument, the - # server still returns regular EXPUNGE responses, which contain - # a sequence number. These will be deleted from - # #responses and this method returns them as an array of - # sequence number integers. - # - # Related: #expunge - # - # ===== Capabilities - # - # The server's capabilities must include +UIDPLUS+ - # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]]. - def uid_expunge(uid_set) - synchronize do - send_command("UID EXPUNGE", MessageSet.new(uid_set)) - clear_responses("EXPUNGE") - end - end - - # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4] - # to search the mailbox for messages that match the given searching - # criteria, and returns message sequence numbers. +keys+ can either be a - # string holding the entire search string, or a single-dimension array of - # search keywords and arguments. - # - # Returns a SearchResult object. SearchResult inherits from Array (for - # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+ - # capability has been enabled. - # - # Related: #uid_search - # - # ===== Search criteria - # - # For a full list of search criteria, - # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]], - # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]], - # in addition to documentation for - # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]] - # reported by #capabilities which may define additional search filters, e.g: - # +CONDSTORE+, +WITHIN+, +FILTERS+, SEARCH=FUZZY, +OBJECTID+, or - # +SAVEDATE+. The following are some common search criteria: - # - # :: a set of message sequence numbers. "," indicates - # an interval, "+:+" indicates a range. For instance, - # "2,10:12,15" means "2,10,11,12,15". - # - # BEFORE :: messages with an internal date strictly before - # . The date argument has a format similar - # to 8-Aug-2002, and can be formatted using - # Net::IMAP.format_date. - # - # BODY :: messages that contain within their body. - # - # CC :: messages containing in their CC field. - # - # FROM :: messages that contain in their FROM field. - # - # NEW:: messages with the \Recent, but not the \Seen, flag set. - # - # NOT :: negate the following search key. - # - # OR :: "or" two search keys together. - # - # ON :: messages with an internal date exactly equal to , - # which has a format similar to 8-Aug-2002. - # - # SINCE :: messages with an internal date on or after . - # - # SUBJECT :: messages with in their subject. - # - # TO :: messages with in their TO field. - # - # ===== For example: - # - # p imap.search(["SUBJECT", "hello", "NOT", "NEW"]) - # #=> [1, 6, 7, 8] - # - # ===== Capabilities - # - # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported - # and enabled for the selected mailbox, a non-empty SearchResult will - # include a +MODSEQ+ value. - # imap.select("mbox", condstore: true) - # result = imap.search(["SUBJECT", "hi there", "not", "new") - # #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594] - # result.modseq # => 5594 - def search(keys, charset = nil) - return search_internal("SEARCH", keys, charset) - end - - # Sends a {UID SEARCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] - # to search the mailbox for messages that match the given searching - # criteria, and returns unique identifiers (UIDs). - # - # Returns a SearchResult object. SearchResult inherits from Array (for - # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+ - # capability has been enabled. - # - # See #search for documentation of search criteria. - def uid_search(keys, charset = nil) - return search_internal("UID SEARCH", keys, charset) - end - - # :call-seq: - # fetch(set, attr, changedsince: nil) -> array of FetchData - # - # Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5] - # to retrieve data associated with a message in the mailbox. - # - # The +set+ parameter is a number or a range between two numbers, - # or an array of those. The number is a message sequence number, - # where -1 represents a '*' for use in range notation like 100..-1 - # being interpreted as '100:*'. Beware that the +exclude_end?+ - # property of a Range object is ignored, and the contents of a - # range are independent of the order of the range endpoints as per - # the protocol specification, so 1...5, 5..1 and 5...1 are all - # equivalent to 1..5. - # - # +attr+ is a list of attributes to fetch; see the documentation - # for FetchData for a list of valid attributes. - # - # +changedsince+ is an optional integer mod-sequence. It limits results to - # messages with a mod-sequence greater than +changedsince+. - # - # The return value is an array of FetchData. - # - # Related: #uid_search, FetchData - # - # ===== For example: - # - # p imap.fetch(6..8, "UID") - # #=> [#98}>, \\ - # #99}>, \\ - # #100}>] - # p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]") - # #=> [#"Subject: test\r\n\r\n"}>] - # data = imap.uid_fetch(98, ["RFC822.SIZE", "INTERNALDATE"])[0] - # p data.seqno - # #=> 6 - # p data.attr["RFC822.SIZE"] - # #=> 611 - # p data.attr["INTERNALDATE"] - # #=> "12-Oct-2000 22:40:59 +0900" - # p data.attr["UID"] - # #=> 98 - # - # ===== Capabilities - # - # Many extensions define new message +attr+ names. See FetchData for a list - # of supported extension fields. - # - # The server's capabilities must include +CONDSTORE+ - # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the - # +changedsince+ argument. Using +changedsince+ implicitly enables the - # +CONDSTORE+ extension. - def fetch(set, attr, mod = nil, changedsince: nil) - fetch_internal("FETCH", set, attr, mod, changedsince: changedsince) - end - - # :call-seq: - # uid_fetch(set, attr, changedsince: nil) -> array of FetchData - # - # Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] - # to retrieve data associated with a message in the mailbox. - # - # Similar to #fetch, but the +set+ parameter contains unique identifiers - # instead of message sequence numbers. - # - # >>> - # *Note:* Servers _MUST_ implicitly include the +UID+ message data item as - # part of any +FETCH+ response caused by a +UID+ command, regardless of - # whether a +UID+ was specified as a message data item to the +FETCH+. - # - # Related: #fetch, FetchData - # - # ===== Capabilities - # Same as #fetch. - def uid_fetch(set, attr, mod = nil, changedsince: nil) - fetch_internal("UID FETCH", set, attr, mod, changedsince: changedsince) - end - - # :call-seq: - # store(set, attr, value, unchangedsince: nil) -> array of FetchData - # - # Sends a {STORE command [IMAP4rev1 §6.4.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.6] - # to alter data associated with messages in the mailbox, in particular their - # flags. - # - # +set+ is a number, an array of numbers, or a Range object. Each number is - # a message sequence number. - # - # +attr+ is the name of a data item to store. The semantics of +value+ - # varies based on +attr+: - # * When +attr+ is "FLAGS", the flags in +value+ replace the - # message's flag list. - # * When +attr+ is "+FLAGS", the flags in +value+ are added to - # the flags for the message. - # * When +attr+ is "-FLAGS", the flags in +value+ are removed - # from the message. - # - # +unchangedsince+ is an optional integer mod-sequence. It prohibits any - # changes to messages with +mod-sequence+ greater than the specified - # +unchangedsince+ value. A SequenceSet of any messages that fail this - # check will be returned in a +MODIFIED+ ResponseCode. - # - # The return value is an array of FetchData. - # - # Related: #uid_store - # - # ===== For example: - # - # p imap.store(6..8, "+FLAGS", [:Deleted]) - # #=> [#[:Seen, :Deleted]}>, - # #[:Seen, :Deleted]}>, - # #[:Seen, :Deleted]}>] - # - # ===== Capabilities - # - # Extensions may define new data items to be used with #store. - # - # The server's capabilities must include +CONDSTORE+ - # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the - # +unchangedsince+ argument. Using +unchangedsince+ implicitly enables the - # +CONDSTORE+ extension. - def store(set, attr, flags, unchangedsince: nil) - store_internal("STORE", set, attr, flags, unchangedsince: unchangedsince) - end - - # :call-seq: - # uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData - # - # Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] - # to alter data associated with messages in the mailbox, in particular their - # flags. - # - # Similar to #store, but +set+ contains unique identifiers instead of - # message sequence numbers. - # - # Related: #store - # - # ===== Capabilities - # Same as #store. - def uid_store(set, attr, flags, unchangedsince: nil) - store_internal("UID STORE", set, attr, flags, unchangedsince: unchangedsince) - end - - # Sends a {COPY command [IMAP4rev1 §6.4.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.7] - # to copy the specified message(s) to the end of the specified destination - # +mailbox+. The +set+ parameter is a number, an array of numbers, or a - # Range object. The number is a message sequence number. - # - # Related: #uid_copy - # - # ===== Capabilities - # - # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is - # supported, the server's response should include a +COPYUID+ response code - # with UIDPlusData. This will report the UIDVALIDITY of the destination - # mailbox, the UID set of the source messages, and the assigned UID set of - # the moved messages. - def copy(set, mailbox) - copy_internal("COPY", set, mailbox) - end - - # Sends a {UID COPY command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] - # to copy the specified message(s) to the end of the specified destination - # +mailbox+. - # - # Similar to #copy, but +set+ contains unique identifiers. - # - # ===== Capabilities - # - # +UIDPLUS+ affects #uid_copy the same way it affects #copy. - def uid_copy(set, mailbox) - copy_internal("UID COPY", set, mailbox) - end - - # Sends a {MOVE command [RFC6851 §3.1]}[https://www.rfc-editor.org/rfc/rfc6851#section-3.1] - # {[IMAP4rev2 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.8] - # to move the specified message(s) to the end of the specified destination - # +mailbox+. The +set+ parameter is a number, an array of numbers, or a - # Range object. The number is a message sequence number. - # - # Related: #uid_move - # - # ===== Capabilities - # - # The server's capabilities must include +MOVE+ - # [RFC6851[https://tools.ietf.org/html/rfc6851]]. - # - # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is - # supported, the server's response should include a +COPYUID+ response code - # with UIDPlusData. This will report the UIDVALIDITY of the destination - # mailbox, the UID set of the source messages, and the assigned UID set of - # the moved messages. - # - def move(set, mailbox) - copy_internal("MOVE", set, mailbox) - end - - # Sends a {UID MOVE command [RFC6851 §3.2]}[https://www.rfc-editor.org/rfc/rfc6851#section-3.2] - # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9] - # to move the specified message(s) to the end of the specified destination - # +mailbox+. - # - # Similar to #move, but +set+ contains unique identifiers. - # - # Related: #move - # - # ===== Capabilities - # - # Same as #move: The server's capabilities must include +MOVE+ - # [RFC6851[https://tools.ietf.org/html/rfc6851]]. +UIDPLUS+ also affects - # #uid_move the same way it affects #move. - def uid_move(set, mailbox) - copy_internal("UID MOVE", set, mailbox) - end - - # Sends a {SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] - # to search a mailbox for messages that match +search_keys+ and return an - # array of message sequence numbers, sorted by +sort_keys+. +search_keys+ - # are interpreted the same as for #search. - # - #-- - # TODO: describe +sort_keys+ - #++ - # - # Related: #uid_sort, #search, #uid_search, #thread, #uid_thread - # - # ===== For example: - # - # p imap.sort(["FROM"], ["ALL"], "US-ASCII") - # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9] - # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII") - # #=> [6, 7, 8, 1] - # - # ===== Capabilities - # - # The server's capabilities must include +SORT+ - # [RFC5256[https://tools.ietf.org/html/rfc5256]]. - def sort(sort_keys, search_keys, charset) - return sort_internal("SORT", sort_keys, search_keys, charset) - end - - # Sends a {UID SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] - # to search a mailbox for messages that match +search_keys+ and return an - # array of unique identifiers, sorted by +sort_keys+. +search_keys+ are - # interpreted the same as for #search. - # - # Related: #sort, #search, #uid_search, #thread, #uid_thread - # - # ===== Capabilities - # - # The server's capabilities must include +SORT+ - # [RFC5256[https://tools.ietf.org/html/rfc5256]]. - def uid_sort(sort_keys, search_keys, charset) - return sort_internal("UID SORT", sort_keys, search_keys, charset) - end - - # Sends a {THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] - # to search a mailbox and return message sequence numbers in threaded - # format, as a ThreadMember tree. +search_keys+ are interpreted the same as - # for #search. - # - # The supported algorithms are: - # - # ORDEREDSUBJECT:: split into single-level threads according to subject, - # ordered by date. - # REFERENCES:: split into threads by parent/child relationships determined - # by which message is a reply to which. - # - # Unlike #search, +charset+ is a required argument. US-ASCII - # and UTF-8 are sample values. - # - # Related: #uid_thread, #search, #uid_search, #sort, #uid_sort - # - # ===== Capabilities - # - # The server's capabilities must include +THREAD+ - # [RFC5256[https://tools.ietf.org/html/rfc5256]]. - def thread(algorithm, search_keys, charset) - return thread_internal("THREAD", algorithm, search_keys, charset) - end - - # Sends a {UID THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] - # Similar to #thread, but returns unique identifiers instead of - # message sequence numbers. - # - # Related: #thread, #search, #uid_search, #sort, #uid_sort - # - # ===== Capabilities - # - # The server's capabilities must include +THREAD+ - # [RFC5256[https://tools.ietf.org/html/rfc5256]]. - def uid_thread(algorithm, search_keys, charset) - return thread_internal("UID THREAD", algorithm, search_keys, charset) - end - - # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1] - # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1] - # to enable the specified server +capabilities+. Each capability may be an - # array, string, or symbol. Returns a list of the capabilities that were - # enabled. - # - # The +ENABLE+ command is only valid in the _authenticated_ state, before - # any mailbox is selected. - # - # Related: #capable?, #capabilities, #capability - # - # ===== Capabilities - # - # The server's capabilities must include - # +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]] - # or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]]. - # - # Additionally, the server capabilities must include a capability matching - # each enabled extension (usually the same name as the enabled extension). - # The following capabilities may be enabled: - # - # [+CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]] - # - # Updates various commands to return +CONDSTORE+ extension responses. It - # is not necessary to explicitly enable +CONDSTORE+—using any of the - # command parameters defined by the extension will implicitly enable it. - # See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1]. - # - # [+:utf8+ --- an alias for "UTF8=ACCEPT"] - # - # In a future release, enable(:utf8) will enable either - # "UTF8=ACCEPT" or "IMAP4rev2", depending on server - # capabilities. - # - # ["UTF8=ACCEPT" [RFC6855[https://tools.ietf.org/html/rfc6855]]] - # - # The server's capabilities must include UTF8=ACCEPT _or_ - # UTF8=ONLY. - # - # This allows the server to send strings encoded as UTF-8 which might - # otherwise need to use a 7-bit encoding, such as {modified - # UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for - # message headers. - # - # *Note:* A future update may set string encodings slightly - # differently, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8" - # when it is. Currently, the encoding of strings sent as "quoted" or - # "text" will _always_ be "UTF-8", even when only ASCII characters are - # used (e.g. "Subject: Agenda") And currently, string "literals" sent - # by the server will always have an "ASCII-8BIT" (binary) - # encoding, even if they generally contain UTF-8 data, if they are - # text at all. - # - # ["UTF8=ONLY" [RFC6855[https://tools.ietf.org/html/rfc6855]]] - # - # A server that reports the UTF8=ONLY capability _requires_ that - # the client enable("UTF8=ACCEPT") before any mailboxes may be - # selected. For convenience, enable("UTF8=ONLY") is aliased to - # enable("UTF8=ACCEPT"). - # - # ===== Unsupported capabilities - # - # *Note:* Some extensions that use ENABLE permit the server to send syntax - # that Net::IMAP cannot parse, which may raise an exception and disconnect. - # Some extensions may work, but the support may be incomplete, untested, or - # experimental. - # - # Until a capability is documented here as supported, enabling it may result - # in undocumented behavior and a future release may update with incompatible - # behavior without warning or deprecation. - # - # Caution is advised. - # - def enable(*capabilities) - capabilities = capabilities - .flatten - .map {|e| ENABLE_ALIASES[e] || e } - .uniq - .join(' ') - synchronize do - send_command("ENABLE #{capabilities}") - result = clear_responses("ENABLED").last || [] - @utf8_strings ||= result.include? "UTF8=ACCEPT" - @utf8_strings ||= result.include? "IMAP4REV2" - result - end - end - - # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3] - # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13] - # that waits for notifications of new or expunged messages. Yields - # responses from the server during the IDLE. - # - # Use #idle_done to leave IDLE. - # - # If +timeout+ is given, this method returns after +timeout+ seconds passed. - # +timeout+ can be used for keep-alive. For example, the following code - # checks the connection for each 60 seconds. - # - # loop do - # imap.idle(60) do |res| - # ... - # end - # end - # - # Related: #idle_done, #noop, #check - # - # ===== Capabilities - # - # The server's capabilities must include +IDLE+ - # [RFC2177[https://tools.ietf.org/html/rfc2177]]. - def idle(timeout = nil, &response_handler) - raise LocalJumpError, "no block given" unless response_handler - - response = nil - - synchronize do - tag = Thread.current[:net_imap_tag] = generate_tag - put_string("#{tag} IDLE#{CRLF}") - - begin - add_response_handler(&response_handler) - @idle_done_cond = new_cond - @idle_done_cond.wait(timeout) - @idle_done_cond = nil - if @receiver_thread_terminating - raise @exception || Net::IMAP::Error.new("connection closed") - end - ensure - unless @receiver_thread_terminating - remove_response_handler(response_handler) - put_string("DONE#{CRLF}") - response = get_tagged_response(tag, "IDLE", @idle_response_timeout) - end - end - end - - return response - end - - # Leaves IDLE. - # - # Related: #idle - def idle_done - synchronize do - if @idle_done_cond.nil? - raise Net::IMAP::Error, "not during IDLE" - end - @idle_done_cond.signal - end - end - - # :call-seq: - # responses {|hash| ...} -> block result - # responses(type) {|array| ...} -> block result - # - # Yields unhandled responses and returns the result of the block. - # - # Unhandled responses are stored in a hash, with arrays of - # non-+nil+ UntaggedResponse#data keyed by UntaggedResponse#name - # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to - # yield the entire responses hash. Call with +type+ to yield only the array - # of responses for that type. - # - # For example: - # - # imap.select("inbox") - # p imap.responses("EXISTS", &:last) - # #=> 2 - # p imap.responses("UIDVALIDITY", &:last) - # #=> 968263756 - # - # >>> - # *Note:* Access to the responses hash is synchronized for thread-safety. - # The receiver thread and response_handlers cannot process new responses - # until the block completes. Accessing either the response hash or its - # response type arrays outside of the block is unsafe. - # - # Calling without a block is unsafe and deprecated. Future releases will - # raise ArgumentError unless a block is given. - # - # Previously unhandled responses are automatically cleared before entering a - # mailbox with #select or #examine. Long-lived connections can receive many - # unhandled server responses, which must be pruned or they will continually - # consume more memory. Update or clear the responses hash or arrays inside - # the block, or use #clear_responses. - # - # Only non-+nil+ data is stored. Many important response codes have no data - # of their own, but are used as "tags" on the ResponseText object they are - # attached to. ResponseText will be accessible by its response types: - # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+". - # - # TaggedResponse#data is not saved to #responses, nor is any - # ResponseCode#data on tagged responses. Although some command methods do - # return the TaggedResponse directly, #add_response_handler must be used to - # handle all response codes. - # - # Related: #clear_responses, #response_handlers, #greeting - def responses(type = nil) - if block_given? - synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) } - elsif type - raise ArgumentError, "Pass a block or use #clear_responses" - else - # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1) - @responses - end - end - - # :call-seq: - # clear_responses -> hash - # clear_responses(type) -> array - # - # Clears and returns the unhandled #responses hash or the unhandled - # responses array for a single response +type+. - # - # Clearing responses is synchronized with other threads. The lock is - # released before returning. - # - # Related: #responses, #response_handlers - def clear_responses(type = nil) - synchronize { - if type - @responses.delete(type) || [] - else - @responses.dup.transform_values(&:freeze) - .tap { _1.default = [].freeze } - .tap { @responses.clear } - end - } - .freeze - end - - # Returns all response handlers, including those that are added internally - # by commands. Each response handler will be called with every new - # UntaggedResponse, TaggedResponse, and ContinuationRequest. - # - # Response handlers are called with a mutex inside the receiver thread. New - # responses cannot be processed and commands from other threads must wait - # until all response_handlers return. An exception will shut-down the - # receiver thread and close the connection. - # - # For thread-safety, the returned array is a frozen copy of the internal - # array. - # - # Related: #add_response_handler, #remove_response_handler - def response_handlers - synchronize { @response_handlers.clone.freeze } - end - - # Adds a response handler. For example, to detect when - # the server sends a new EXISTS response (which normally - # indicates new messages being added to the mailbox), - # add the following handler after selecting the - # mailbox: - # - # imap.add_response_handler { |resp| - # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS" - # puts "Mailbox now has #{resp.data} messages" - # end - # } - # - # Related: #remove_response_handler, #response_handlers - def add_response_handler(handler = nil, &block) - raise ArgumentError, "two Procs are passed" if handler && block - synchronize do - @response_handlers.push(block || handler) - end - end - - # Removes the response handler. - # - # Related: #add_response_handler, #response_handlers - def remove_response_handler(handler) - synchronize do - @response_handlers.delete(handler) - end - end - - private - - CRLF = "\r\n" # :nodoc: - PORT = 143 # :nodoc: - SSL_PORT = 993 # :nodoc: - - @@debug = false - - def start_imap_connection - @greeting = get_server_greeting - @capabilities = capabilities_from_resp_code @greeting - @receiver_thread = start_receiver_thread - rescue Exception - @sock.close - raise - end - - def get_server_greeting - greeting = get_response - raise Error, "No server greeting - connection closed" unless greeting - record_untagged_response_code greeting - raise ByeResponseError, greeting if greeting.name == "BYE" - greeting - end - - def start_receiver_thread - Thread.start do - receive_responses - rescue Exception => ex - @receiver_thread_exception = ex - # don't exit the thread with an exception - end - end - - def tcp_socket(host, port) - s = Socket.tcp(host, port, :connect_timeout => @open_timeout) - s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true) - s - rescue Errno::ETIMEDOUT - raise Net::OpenTimeout, "Timeout to open TCP connection to " + - "#{host}:#{port} (exceeds #{@open_timeout} seconds)" - end - - def receive_responses - connection_closed = false - until connection_closed - synchronize do - @exception = nil - end - begin - resp = get_response - rescue Exception => e - synchronize do - @sock.close - @exception = e - end - break - end - unless resp - synchronize do - @exception = EOFError.new("end of file reached") - end - break - end - begin - synchronize do - case resp - when TaggedResponse - @tagged_responses[resp.tag] = resp - @tagged_response_arrival.broadcast - case resp.tag - when @logout_command_tag - return - when @continued_command_tag - @continuation_request_exception = - RESPONSE_ERRORS[resp.name].new(resp) - @continuation_request_arrival.signal - end - when UntaggedResponse - record_untagged_response(resp) - if resp.name == "BYE" && @logout_command_tag.nil? - @sock.close - @exception = ByeResponseError.new(resp) - connection_closed = true - end - when ContinuationRequest - @continuation_request_arrival.signal - end - @response_handlers.each do |handler| - handler.call(resp) - end - end - rescue Exception => e - @exception = e - synchronize do - @tagged_response_arrival.broadcast - @continuation_request_arrival.broadcast - end - end - end - synchronize do - @receiver_thread_terminating = true - @tagged_response_arrival.broadcast - @continuation_request_arrival.broadcast - if @idle_done_cond - @idle_done_cond.signal - end - end - end - - def get_tagged_response(tag, cmd, timeout = nil) - if timeout - deadline = Time.now + timeout - end - until @tagged_responses.key?(tag) - raise @exception if @exception - if timeout - timeout = deadline - Time.now - if timeout <= 0 - return nil - end - end - @tagged_response_arrival.wait(timeout) - end - resp = @tagged_responses.delete(tag) - case resp.name - when /\A(?:OK)\z/ni - return resp - when /\A(?:NO)\z/ni - raise NoResponseError, resp - when /\A(?:BAD)\z/ni - raise BadResponseError, resp - else - disconnect - raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw.chomp] - end - end - - def get_response - buff = String.new - while true - s = @sock.gets(CRLF) - break unless s - buff.concat(s) - if /\{(\d+)\}\r\n/n =~ s - s = @sock.read($1.to_i) - buff.concat(s) - else - break - end - end - return nil if buff.length == 0 - if @@debug - $stderr.print(buff.gsub(/^/n, "S: ")) - end - return @parser.parse(buff) - end - - ############################# - # built-in response handlers - - # store name => [..., data] - def record_untagged_response(resp) - @responses[resp.name] << resp.data - record_untagged_response_code resp - end - - # store code.name => [..., code.data] - def record_untagged_response_code(resp) - return unless resp.data.is_a?(ResponseText) - return unless (code = resp.data.code) - @responses[code.name] << code.data - end - - # NOTE: only call this for greeting, login, and authenticate - def capabilities_from_resp_code(resp) - return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name } - return unless (code = resp.data.code) - return unless code.name.casecmp?("CAPABILITY") - code.data.freeze - end - - ############################# - - # Calls send_command, yielding the text of each ContinuationRequest and - # responding with each block result. Returns TaggedResponse. Raises - # NoResponseError or BadResponseError. - def send_command_with_continuations(cmd, *args) - send_command(cmd, *args) do |server_response| - if server_response.instance_of?(ContinuationRequest) - client_response = yield server_response.data.text - put_string(client_response + CRLF) - end - end - end - - def send_command(cmd, *args, &block) - synchronize do - args.each do |i| - validate_data(i) - end - tag = generate_tag - put_string(tag + " " + cmd) - args.each do |i| - put_string(" ") - send_data(i, tag) - end - put_string(CRLF) - if cmd == "LOGOUT" - @logout_command_tag = tag - end - if block - add_response_handler(&block) - end - begin - return get_tagged_response(tag, cmd) - ensure - if block - remove_response_handler(block) - end - end - end - end - - def generate_tag - @tagno += 1 - return format("%s%04d", @tag_prefix, @tagno) - end - - def put_string(str) - @sock.print(str) - if @@debug - if @debug_output_bol - $stderr.print("C: ") - end - $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " }) - if /\n\z/n.match(str) - @debug_output_bol = true - else - @debug_output_bol = false - end - end - end - - def search_internal(cmd, keys, charset) - if keys.instance_of?(String) - keys = [RawData.new(keys)] - else - normalize_searching_criteria(keys) - end - synchronize do - if charset - send_command(cmd, "CHARSET", charset, *keys) - else - send_command(cmd, *keys) - end - clear_responses("SEARCH").last || [] - end - end - - def fetch_internal(cmd, set, attr, mod = nil, changedsince: nil) - if changedsince - mod ||= [] - mod << "CHANGEDSINCE" << Integer(changedsince) - end - case attr - when String then - attr = RawData.new(attr) - when Array then - attr = attr.map { |arg| - arg.is_a?(String) ? RawData.new(arg) : arg - } - end - - synchronize do - clear_responses("FETCH") - if mod - send_command(cmd, MessageSet.new(set), attr, mod) - else - send_command(cmd, MessageSet.new(set), attr) - end - clear_responses("FETCH") - end - end - - def store_internal(cmd, set, attr, flags, unchangedsince: nil) - attr = RawData.new(attr) if attr.instance_of?(String) - args = [MessageSet.new(set)] - args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince - args << attr << flags - synchronize do - clear_responses("FETCH") - send_command(cmd, *args) - clear_responses("FETCH") - end - end - - def copy_internal(cmd, set, mailbox) - send_command(cmd, MessageSet.new(set), mailbox) - end - - def sort_internal(cmd, sort_keys, search_keys, charset) - if search_keys.instance_of?(String) - search_keys = [RawData.new(search_keys)] - else - normalize_searching_criteria(search_keys) - end - synchronize do - send_command(cmd, sort_keys, charset, *search_keys) - clear_responses("SORT").last || [] - end - end - - def thread_internal(cmd, algorithm, search_keys, charset) - if search_keys.instance_of?(String) - search_keys = [RawData.new(search_keys)] - else - normalize_searching_criteria(search_keys) - end - synchronize do - send_command(cmd, algorithm, charset, *search_keys) - clear_responses("THREAD").last || [] - end - end - - def normalize_searching_criteria(keys) - keys.collect! do |i| - case i - when -1, Range, Array - MessageSet.new(i) - else - i - end - end - end - - def build_ssl_ctx(ssl) - if ssl - params = (Hash.try_convert(ssl) || {}).freeze - context = SSLContext.new - context.set_params(params) - if defined?(VerifyCallbackProc) - context.verify_callback = VerifyCallbackProc - end - context.freeze - [params, context] - else - false - end - end - - def start_tls_session - raise "SSL extension not installed" unless defined?(OpenSSL::SSL) - raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket) - raise "cannot start TLS without SSLContext" unless ssl_ctx - @sock = SSLSocket.new(@sock, ssl_ctx) - @sock.sync_close = true - @sock.hostname = @host if @sock.respond_to? :hostname= - ssl_socket_connect(@sock, @open_timeout) - if ssl_ctx.verify_mode != VERIFY_NONE - @sock.post_connection_check(@host) - @tls_verified = true - end - end - - def sasl_adapter - SASLAdapter.new(self, &method(:send_command_with_continuations)) - end - - #-- - # We could get the saslprep method by extending the SASLprep module - # directly. It's done indirectly, so SASLprep can be lazily autoloaded, - # because most users won't need it. - #++ - # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep. - def self.saslprep(string, **opts) - Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts) - end - - end -end - -require_relative "imap/errors" -require_relative "imap/command_data" -require_relative "imap/data_encoding" -require_relative "imap/flags" -require_relative "imap/response_data" -require_relative "imap/response_parser" -require_relative "imap/authenticators" - -require_relative "imap/deprecated_client_options" -Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/net-imap.gemspec ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/net-imap.gemspec --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/net-imap.gemspec 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/net-imap.gemspec 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb"), :encoding=> 'utf-8') do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Shugo Maeda", "nicholas a. evans"] - spec.email = ["shugo@ruby-lang.org", "nick@ekenosen.net"] - - spec.summary = %q{Ruby client api for Internet Message Access Protocol} - spec.description = %q{Ruby client api for Internet Message Access Protocol} - spec.homepage = "https://github.com/ruby/net-imap" - spec.required_ruby_version = Gem::Requirement.new(">= 2.7.3") - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - spec.metadata["changelog_uri"] = spec.homepage + "/releases" - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>/dev/null`.split("\x0") - .reject {|f| f.match(%r{^(bin|test|spec|benchmarks|features|rfcs)/}) } - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] - - spec.add_dependency "net-protocol" - spec.add_dependency "date" - - spec.add_development_dependency "digest" - spec.add_development_dependency "strscan" -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/benchmarks.rake ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/benchmarks.rake --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/benchmarks.rake 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/benchmarks.rake 1970-01-01 00:00:00.000000000 +0000 @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -PARSER_TEST_FIXTURES = FileList.new "test/net/imap/fixtures/response_parser/*.yml" -CLOBBER.include "benchmarks/parser.yml" -CLEAN.include "benchmarks/Gemfile-*" - -BENCHMARK_INIT = < PARSER_TEST_FIXTURES do |t| - require "yaml" - require "pathname" - require "net/imap" - - path = Pathname.new(__dir__) / "../test/net/imap/fixtures/response_parser" - files = path.glob("*.yml") - tests = files.flat_map {|file| - file.read - .gsub(%r{([-:]) !ruby/(object|struct):\S+}) { $1 } - .then { - YAML.safe_load(_1, filename: file, - permitted_classes: [Symbol, Regexp], aliases: true) - } - .fetch(:tests) - .select {|test_name, test| - :parser_assert_equal == test.fetch(:test_type) { - test.key?(:expected) ? :parser_assert_equal : :parser_pending - } - } - .map {|test_name, test| [test_name.to_s, test.fetch(:response)] } - } - - benchmarks = tests.map {|fixture_name, response| - {"name" => fixture_name.delete_prefix("test_"), - "prelude" => "response = -%s.b" % [response.dump], - "script" => "parser.parse(response)"} - } - .sort_by { _1["name"] } - - YAML.dump({"prelude" => BENCHMARK_INIT, "benchmark" => benchmarks}) - .then { File.write t.name, _1 } -end - -namespace :benchmarks do - desc "Generate benchmarks from fixture data" - task :generate => "benchmarks/parser.yml" - - desc "run the parser benchmarks comparing multiple gem versions" - task :compare => :generate do |task, args| - cd Pathname.new(__dir__) + ".." - current = `git describe --tags --dirty`.chomp - current = "dev" if current.empty? - versions = args.to_a - if versions.empty? - latest = %x{git describe --tags --abbrev=0 --match 'v*.*.*'}.chomp - versions = latest.empty? ? [] : [latest.delete_prefix("v")] - end - versions = versions.to_h { [_1, "Gemfile-v#{_1}"] } - cd "benchmarks" do - versions.each do |version, gemfile| - File.write gemfile, <<~RUBY - # frozen_string_literal: true - source "https://rubygems.org" - gem "net-imap", #{version.dump} - RUBY - end - versions = {current => "../Gemfile" , **versions}.map { - "%s::/usr/bin/env BUNDLE_GEMFILE=%s ruby" % _1 - }.join(";") - - extra = ENV.fetch("BENCHMARK_ARGS", "").shellsplit - - sh("benchmark-driver", - "--bundler", - "-e", versions, - "parser.yml", - *extra) - end - end - -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/rdoc.rake ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/rdoc.rake --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/rdoc.rake 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/rdoc.rake 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -# require "sdoc" -require "rdoc/task" -require_relative "../lib/net/imap" -require 'rdoc/rdoc' unless defined?(RDoc::Markup::ToHtml) - -module RDoc::Generator - module NetIMAP - - module RemoveRedundantParens - def param_seq - super.sub(/^\(\)\s*/, "") - end - end - - # See https://github.com/ruby/rdoc/pull/936 - module FixSectionComments - def markup(text) - @store ||= @parent&.store - super - end - def description; markup comment end - def comment; super || @comments&.first end - def parse(_comment_location = nil) super() end - end - - # render "[label] data" lists as tables. adapted from "hanna-nouveau" gem. - module LabelListTable - def list_item_start(list_item, list_type) - case list_type - when :NOTE - %(#{Array(list_item.label).map{|label| to_html(label)}.join("
")}) - else - super - end - end - - def list_end_for(list_type) - case list_type - when :NOTE then - "" - else - super - end - end - end - - end -end - -class RDoc::AnyMethod - prepend RDoc::Generator::NetIMAP::RemoveRedundantParens -end - -class RDoc::Context::Section - prepend RDoc::Generator::NetIMAP::FixSectionComments -end - -class RDoc::Markup::ToHtml - LIST_TYPE_TO_HTML[:NOTE] = ['', '
'] - prepend RDoc::Generator::NetIMAP::LabelListTable -end - -RDoc::Task.new do |doc| - doc.main = "README.md" - doc.title = "net-imap #{Net::IMAP::VERSION}" - doc.rdoc_dir = "doc" - doc.rdoc_files = FileList.new %w[lib/**/*.rb *.rdoc *.md] - doc.options << "--template-stylesheets" << "docs/styles.css" - # doc.generator = "hanna" -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/rfcs.rake ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/rfcs.rake --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/rfcs.rake 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/rfcs.rake 1970-01-01 00:00:00.000000000 +0000 @@ -1,168 +0,0 @@ -# frozen_string_literal: true - -RFCS = { - - # Historic IMAP RFCs - 822 => "Internet Message Format (OBSOLETE)", - 1730 => "IMAP4 (OBSOLETE)", - 1731 => "IMAP4 Authentication Mechanisms (OBSOLETE)", - 2060 => "IMAP4rev1 (OBSOLETE)", - 2061 => "IMAP4 Compatibility with IMAP2bis", - 2062 => "Internet Message Access Protocol - Obsolete Syntax", - 2086 => "IMAP ACL (OBSOLETE)", - 2087 => "IMAP QUOTA (OBSOLETE)", - 2088 => "IMAP LITERAL+ (OBSOLETE)", - 2095 => "IMAP/POP AUTHorize Extension for CRAM-MD5 (OBSOLETE)", - 2192 => "IMAP URL Scheme (OBSOLETE)", - 2222 => "SASL (OBSOLETE)", - 2359 => "IMAP UIDPLUS (OBSOLETE)", - 2822 => "Internet Message Format (OBSOLETE)", - 3348 => "IMAP CHILDREN (OBSOLETED)", - 4551 => "IMAP CONDSTORE (OBSOLETE)", - 5162 => "IMAP QRESYNC (OBSOLETE)", - 6237 => "IMAP MULTISEARCH (OBSOLETE)", - - # Core IMAP RFCs - 3501 => "IMAP4rev1", # supported by nearly all email servers - 4466 => "Collected Extensions to IMAP4 ABNF", - 9051 => "IMAP4rev2", # not widely supported yet - - # RFC-9051 Normative References (not a complete list) - 2152 => "UTF-7", - 2180 => "IMAP4 Multi-Accessed Mailbox Practice", - 2683 => "IMAP4 Implementation Recommendations", - 3503 => "Message Disposition Notification (MDN) profile IMAP", - 5234 => "ABNF", - 5788 => "IMAP4 keyword registry", - - # Internet Message format and envelope and body structure - 5322 => "Internet Message Format (current)", - - 1864 => "[MD5]: The Content-MD5 Header Field", - 2045 => "[MIME-IMB]: MIME Part One: Format of Internet Message Bodies", - 2046 => "[MIME-IMT]: MIME Part Two: Media Types", - 2047 => "[MIME-HDRS]: MIME Part Three: Header Extensions for Non-ASCII Text", - 2183 => "[DISPOSITION]: The Content-Disposition Header", - 2231 => "MIME Parameter Value and Encoded Word Extensions: " \ - "Character Sets, Languages, and Continuations", - 2557 => "[LOCATION]: MIME Encapsulation of Aggregate Documents", - 2978 => "[CHARSET]: IANA Charset Registration Procedures, BCP 19", - 3282 => "[LANGUAGE-TAGS]: Content Language Headers", - 6532 => "[I18N-HDRS]: Internationalized Email Headers", - - # SASL - 4422 => "SASL, EXTERNAL", - - # stringprep - 3454 => "stringprep", - 4013 => "SASLprep", - 8265 => "PRECIS", # obsoletes SASLprep? - - # SASL mechanisms (not a complete list) - 2195 => "SASL CRAM-MD5", - 4505 => "SASL ANONYMOUS", - 4616 => "SASL PLAIN", - 4752 => "SASL GSSAPI (Kerberos V5)", - 5801 => "SASL GS2-*, GS2-KRB5", - 5802 => "SASL SCRAM-*, SCRAM-SHA-1, SCRAM-SHA1-PLUS", - 5803 => "LDAP Schema for Storing SCRAM Secrets", - 6331 => "SASL DIGEST-MD5", - 6595 => "SASL SAML20", - 6616 => "SASL OPENID20", - 7628 => "SASL OAUTH10A, OAUTHBEARER", - 7677 => "SASL SCRAM-SHA-256, SCRAM-SHA256-PLUS", - - # "Informational" RFCs - 1733 => "Distributed E-Mail Models in IMAP4", - 4549 => "Synchronization Operations for Disconnected IMAP4 Clients", - - # TLS and other security concerns - 2595 => "Using TLS with IMAP, POP3 and ACAP", - 6151 => "Updated Security Considerations for MD5 Message-Digest and HMAC-MD5", - 7525 => "Recommendations for Secure Use of TLS and DTLS", - 7818 => "Updated TLS Server Identity Check Procedure for Email Protocols", - 8314 => "Cleartext Considered Obsolete: Use of TLS for Email", - 8996 => "Deprecating TLS 1.0 and TLS 1.1,", - - # related email specifications - 6376 => "DomainKeys Identified Mail (DKIM) Signatures", - 6409 => "Message Submission for Mail", - - # Other IMAP4 "Standards Track" RFCs - 5092 => "IMAP URL Scheme", - 5593 => "IMAP URL Access Identifier Extension", - 5530 => "IMAP Response Codes", - 6186 => "Use of SRV Records for Locating Email Submission/Access Services", - 8305 => "Happy Eyeballs Version 2: Better Connectivity Using Concurrency", - - # IMAP4 Extensions - 2177 => "IMAP IDLE", - 2193 => "IMAP MAILBOX-REFERRALS", - 2221 => "IMAP LOGIN-REFERRALS", - 2342 => "IMAP NAMESPACE", - 2971 => "IMAP ID", - 3502 => "IMAP MULTIAPPEND", - 3516 => "IMAP BINARY", - 3691 => "IMAP UNSELECT", - 4314 => "IMAP ACL, RIGHTS=", - 4315 => "IMAP UIDPLUS", - 4467 => "IMAP URLAUTH", - 4469 => "IMAP CATENATE", - 4731 => "IMAP ESEARCH", - 4959 => "IMAP SASL-IR", - 4978 => "IMAP COMPRESS=DEFLATE", - 5032 => "IMAP WITHIN", - 5161 => "IMAP ENABLE", - 5182 => "IMAP SEARCHRES", - 5255 => "IMAP I18NLEVEL=1, I18NLEVEL=2, LANGUAGE", - 5256 => "IMAP SORT, THREAD", - 5257 => "IMAP ANNOTATE-EXPERIMENT-1", - 5258 => "IMAP LIST-EXTENDED", - 5259 => "IMAP CONVERT", - 5267 => "IMAP CONTEXT=SEARCH, CONTEXT=SORT, ESORT", - 5464 => "IMAP METADATA, METADATA-SERVER", - 5465 => "IMAP NOTIFY", - 5466 => "IMAP FILTERS", - 5524 => "IMAP URLAUTH=BINARY", # see also: [RFC Errata 6214] - 5550 => "IMAP URL-PARTIAL", - 5738 => "IMAP UTF8=ALL, UTF8=APPEND, UTF8=USER", # OBSOLETED by RFC6855 - 5819 => "IMAP LIST-STATUS", - 5957 => "IMAP SORT=DISPLAY", - 6154 => "IMAP SPECIAL-USE, CREATE-SPECIAL-USE", - 6203 => "IMAP SEARCH=FUZZY", - 6785 => "IMAP IMAPSIEVE=", - 6851 => "IMAP MOVE", - 6855 => "IMAP UTF8=ACCEPT, UTF8=ONLY", - 7162 => "IMAP CONDSTORE, QRESYNC", - 7377 => "IMAP MULTISEARCH", - 7888 => "IMAP LITERAL+, LITERAL-", - 7889 => "IMAP APPENDLIMIT", - 8437 => "IMAP UNAUTHENTICATE", - 8438 => "IMAP STATUS=SIZE", - 8440 => "IMAP LIST-MYRIGHTS", - 8474 => "IMAP OBJECTID", - 8508 => "IMAP REPLACE", - 8514 => "IMAP SAVEDATE", - 8970 => "IMAP PREVIEW", - 9208 => "IMAP QUOTA, QUOTA=, QUOTASET", - - # etc... - 3629 => "UTF8", - 6857 => "Post-Delivery Message Downgrading for I18n Email Messages", - -}.freeze - -task :rfcs => RFCS.keys.map {|n| "rfcs/rfc%04d.txt" % [n] } - -RFC_RE = %r{rfcs/rfc(\d+).*\.txt}.freeze -rule RFC_RE do |t| - require "fileutils" - FileUtils.mkpath "rfcs" - require "net/http" - t.name =~ RFC_RE - rfc_url = URI("https://www.rfc-editor.org/rfc/rfc#$1.txt") - rfc_txt = Net::HTTP.get(rfc_url) - File.write(t.name, rfc_txt) -end - -CLEAN.include "rfcs/rfc*.txt" diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/saslprep.rake ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/saslprep.rake --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/saslprep.rake 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/saslprep.rake 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require_relative "string_prep_tables_generator" - -generator = StringPrepTablesGenerator.new - -file generator.json_filename => generator.json_deps do |t| - generator.generate_json_data_file -end - -directory "lib/net/imap/sasl" - -file "lib/net/imap/stringprep/tables.rb" => generator.rb_deps do |t| - File.write t.name, generator.stringprep_rb -end - -file "lib/net/imap/stringprep/saslprep_tables.rb" => generator.rb_deps do |t| - File.write t.name, generator.saslprep_rb -end - -GENERATED_RUBY = FileList.new( - "lib/net/imap/stringprep/tables.rb", - "lib/net/imap/stringprep/saslprep_tables.rb", -) - -CLEAN.include generator.clean_deps -CLOBBER.include GENERATED_RUBY - -task saslprep_rb: GENERATED_RUBY -task test: :saslprep_rb diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/string_prep_tables_generator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/string_prep_tables_generator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9/rakelib/string_prep_tables_generator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9/rakelib/string_prep_tables_generator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,445 +0,0 @@ -# frozen_string_literal: true - -# Generator for stringprep regexps. -# -# Combines Unicode character classes with generated tables. Generated regexps -# are still used to test that the written regexps conform to the specification. -# Some tables don't match up well with any character properties available to -# ruby's regexp engine. Those use the table-generated regexps. -class StringPrepTablesGenerator - STRINGPREP_RFC_FILE = "rfcs/rfc3454.txt" - STRINGPREP_JSON_FILE = "rfcs/rfc3454-stringprep_tables.json" - - # valid UTF-8 can't contain these codepoints - # checking for them anyway, using /\p{Cs}/ ;) - SURROGATES_RANGE = 0xD800..0xDFFF - - attr_reader :json_filename, :rfc_filename - - def initialize(rfc_filename: STRINGPREP_RFC_FILE, - json_filename: STRINGPREP_JSON_FILE) - @rfc_filename = rfc_filename - @json_filename = json_filename - end - - # for rake deps - def json_deps; Rake::FileList.new __FILE__, STRINGPREP_RFC_FILE end - def rb_deps; Rake::FileList.new __FILE__, STRINGPREP_JSON_FILE end - def clean_deps; Rake::FileList.new STRINGPREP_JSON_FILE end - - def generate_json_data_file - require "json" - rfc_filename - .then(&File.method(:read)) - .then(&method(:parse_rfc_text)) - .then(&JSON.method(:pretty_generate)) - .then {|data| File.write json_filename, data } - end - - def tables; @tables ||= load_tables_and_titles_from_json!.first end - def titles; @titles ||= load_tables_and_titles_from_json!.last end - def ranges; @ranges ||= tables.transform_values(&method(:to_ranges)) end - def arrays; @arrays ||= ranges.transform_values{|t| t.flat_map(&:to_a) } end - def sets; @sets ||= arrays.transform_values(&:to_set) end - def regexps; @regexps ||= arrays.transform_values(&method(:to_regexp)) end - def asgn_regexps; @asgn_regexps || asgn_regexps! end - - def merged_tables_regex(*table_names, negate: false) - table_names - .flat_map(&arrays.method(:fetch)) - .then {|array| to_regexp(array, negate: negate) } - end - - def regexp_for(*names, negate: false) - asgn_regexps[[*names, negate]] ||= merged_tables_regex(*names, negate: negate) - end - - def stringprep_rb - <<~RUBY - # frozen_string_literal: true - - #-- - # This file is generated from RFC3454, by rake. Don't edit directly. - #++ - - module Net::IMAP::StringPrep - - module Tables - - #{asgn_table "A.1"} - - #{asgn_table "B.1"} - - #{asgn_table "B.2"} - - #{asgn_table "B.3"} - - #{asgn_mapping "B.1", ""} - - #{asgn_mapping "B.2"} - - #{asgn_mapping "B.3"} - - #{asgn_table "C.1.1"} - - #{asgn_table "C.1.2"} - - #{asgn_table "C.2.1"} - - #{asgn_table "C.2.2"} - - #{asgn_table "C.3"} - - #{asgn_table "C.4"} - - #{asgn_table "C.5"} - - #{asgn_table "C.6"} - - #{asgn_table "C.7"} - - #{asgn_table "C.8"} - - #{asgn_table "C.9"} - - #{asgn_table "D.1"} - - # Used to check req3 of bidirectional checks - #{asgn_table "D.1", negate: true} - - #{asgn_table "D.2"} - - BIDI_DESC_REQ2 = "A string with RandALCat characters must not contain LCat characters." - - # Bidirectional Characters [StringPrep, §6], Requirement 2 - # >>> - # If a string contains any RandALCat character, the string MUST NOT - # contain any LCat character. - BIDI_FAILS_REQ2 = #{bidi_fails_req2.inspect}.freeze - - BIDI_DESC_REQ3 = "A string with RandALCat characters must start and end with RandALCat characters." - - # Bidirectional Characters [StringPrep, §6], Requirement 3 - # >>> - # If a string contains any RandALCat character, a RandALCat - # character MUST be the first character of the string, and a - # RandALCat character MUST be the last character of the string. - BIDI_FAILS_REQ3 = #{bidi_fails_req3.inspect}.freeze - - # Bidirectional Characters [StringPrep, §6] - BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze - - # Names of each codepoint table in the RFC-3454 appendices - TITLES = { - #{table_titles_rb} - }.freeze - - # Regexps matching each codepoint table in the RFC-3454 appendices - REGEXPS = { - #{table_regexps_rb} - }.freeze - - MAPPINGS = { - "B.1" => [IN_B_1, MAP_B_1].freeze, - "B.2" => [IN_B_2, MAP_B_2].freeze, - "B.3" => [IN_B_3, MAP_B_3].freeze, - }.freeze - - end - end - RUBY - end - - def table_titles_rb(indent = 3) - titles - .map{|t| "%p => %p," % t } - .join("\n#{" "*indent}") - end - - def table_regexps_rb(indent = 3) - asgn_regexps # => { ["A.1", false] => regexp, ... } - .reject {|(_, n), _| n } - .map {|(t, _), _| "%p => %s," % [t, regexp_const_name(t)] } - .join("\n#{" "*indent}") - end - - def saslprep_rb - <<~RUBY - # frozen_string_literal: true - - #-- - # This file is generated from RFC3454, by rake. Don't edit directly. - #++ - - module Net::IMAP::StringPrep - - module SASLprep - - # RFC4013 §2.1 Mapping - mapped to space - # >>> - # non-ASCII space characters (\\StringPrep\\[\\"C.1.2\\"]) that can - # be mapped to SPACE (U+0020) - # - # Equal to \\StringPrep\\[\\"C.1.2\\"]. - # Redefined here to avoid loading StringPrep::Tables unless necessary. - MAP_TO_SPACE = #{regex_str "C.1.2"} - - # RFC4013 §2.1 Mapping - mapped to nothing - # >>> - # the "commonly mapped to nothing" characters - # (\\StringPrep\\[\\"B.1\\"]) that can be mapped to nothing. - # - # Equal to \\StringPrep\\[\\"B.1\\"]. - # Redefined here to avoid loading StringPrep::Tables unless necessary. - MAP_TO_NOTHING = #{regex_str "B.1"} - - # RFC4013 §2.3 Prohibited Output - # >>> - # * Non-ASCII space characters — \\StringPrep\\[\\"C.1.2\\"] - # * ASCII control characters — \\StringPrep\\[\\"C.2.1\\"] - # * Non-ASCII control characters — \\StringPrep\\[\\"C.2.2\\"] - # * Private Use characters — \\StringPrep\\[\\"C.3\\"] - # * Non-character code points — \\StringPrep\\[\\"C.4\\"] - # * Surrogate code points — \\StringPrep\\[\\"C.5\\"] - # * Inappropriate for plain text characters — \\StringPrep\\[\\"C.6\\"] - # * Inappropriate for canonical representation characters — \\StringPrep\\[\\"C.7\\"] - # * Change display properties or deprecated characters — \\StringPrep\\[\\"C.8\\"] - # * Tagging characters — \\StringPrep\\[\\"C.9\\"] - TABLES_PROHIBITED = #{SASL_TABLES_PROHIBITED.inspect}.freeze - - # Adds unassigned (by Unicode 3.2) codepoints to TABLES_PROHIBITED. - # - # RFC4013 §2.5 Unassigned Code Points - # >>> - # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its - # list of unassigned code points. - TABLES_PROHIBITED_STORED = ["A.1", *TABLES_PROHIBITED].freeze - - # A Regexp matching codepoints prohibited by RFC4013 §2.3. - # - # This combines all of the TABLES_PROHIBITED tables. - PROHIBITED_OUTPUT = #{regex_str(*SASL_TABLES_PROHIBITED)} - - # RFC4013 §2.5 Unassigned Code Points - # >>> - # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its - # list of unassigned code points. - # - # Equal to \\StringPrep\\[\\"A.1\\"]. - # Redefined here to avoid loading StringPrep::Tables unless necessary. - UNASSIGNED = #{regex_str "A.1"} - - # A Regexp matching codepoints prohibited by RFC4013 §2.3 and §2.5. - # - # This combines PROHIBITED_OUTPUT and UNASSIGNED. - PROHIBITED_OUTPUT_STORED = Regexp.union( - UNASSIGNED, PROHIBITED_OUTPUT - ).freeze - - # Bidirectional Characters [StringPrep, §6] - # - # A Regexp for strings that don't satisfy StringPrep's Bidirectional - # Characters rules. - # - # Equal to StringPrep::Tables::BIDI_FAILURE. - # Redefined here to avoid loading StringPrep::Tables unless necessary. - BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze - - # A Regexp matching strings prohibited by RFC4013 §2.3 and §2.4. - # - # This combines PROHIBITED_OUTPUT and BIDI_FAILURE. - PROHIBITED = Regexp.union( - PROHIBITED_OUTPUT, BIDI_FAILURE, - ) - - # A Regexp matching strings prohibited by RFC4013 §2.3, §2.4, and §2.5. - # - # This combines PROHIBITED_OUTPUT_STORED and BIDI_FAILURE. - PROHIBITED_STORED = Regexp.union( - PROHIBITED_OUTPUT_STORED, BIDI_FAILURE, - ) - - end - end - RUBY - end - - private - - def parse_rfc_text(rfc3454_text) - titles = {} - tables, = rfc3454_text - .lines - .each_with_object([]) {|line, acc| - current, table = acc.last - case line - when /^([A-D]\.[1-9](?:\.[1-9])?) (.*)/ - titles[$1] = $2 - when /^ {3}-{5} Start Table (\S*)/ - acc << [$1, []] - when /^ {3}-{5} End Table / - acc << [nil, nil] - when /^ {3}([0-9A-F]+); ([ 0-9A-F]*)(?:;[^;]*)$/ # mapping tables - table << [$1, $2.split(/ +/)] if current - when /^ {3}([-0-9A-F]+)(?:;[^;]*)?$/ # regular tables - table << $1 if current - when /^ {3}(.*)/ - raise "expected to match %p" % $1 if current - end - } - .to_h.compact - .transform_values {|t| t.first.size == 2 ? t.to_h : t } - tables["titles"] = titles - tables - end - - def load_tables_and_titles_from_json! - require "json" - @tables = json_filename - .then(&File.method(:read)) - .then(&JSON.method(:parse)) - @titles = @tables.delete "titles" - [@tables, @titles] - end - - def to_ranges(table) - (table.is_a?(Hash) ? table.keys : table) - .map{|range| range.split(?-).map{|cp| Integer cp, 16} } - .map{|s,e| s..(e || s)} - end - - # TODO: DRY with unicode_normalize - def to_map(table) - table = table.to_hash - .transform_keys { Integer _1, 16 } - .transform_keys { [_1].pack("U*") } - .transform_values {|cps| cps.map { Integer _1, 16 } } - .transform_values { _1.pack("U*") } - end - - # Starting from a codepoints array (rather than ranges) to deduplicate merged - # tables. - def to_regexp(codepoints, negate: false) - codepoints - .grep_v(SURROGATES_RANGE) # remove surrogate codepoints from C.5 and D.2 - .uniq - .sort - .chunk_while {|cp1,cp2| cp1 + 1 == cp2 } # find contiguous chunks - .map {|chunk| chunk.map{|cp| "%04x" % cp } } # convert to hex strings - .partition {|chunk| chunk[1] } # ranges vs singles - .then {|ranges, singles| - singles.flatten! - [ - negate ? "^" : "", - singles.flatten.any? ? "\\u{%s}" % singles.join(" ") : "", - ranges.map {|r| "\\u{%s}-\\u{%s}" % [r.first, r.last] }.join, - codepoints.any?(SURROGATES_RANGE) ? "\\p{Cs}" : "", # not necessary :) - ].join - } - .then {|char_class| Regexp.new "[#{char_class}]" } - end - - def asgn_regexps! - @asgn_regexps = {} - # preset the regexp for each table - asgn_regex "A.1", /\p{^AGE=3.2}/ - # If ruby supported all unicode properties (i.e. line break = word joiner): - # /[\u{00ad 034f 1806}\p{join_c}\p{VS}\p{lb=WJ}&&\p{age=3.2}]/ - asgn_table "B.1" - asgn_table "B.2" - asgn_table "B.3" - asgn_regex "C.1.1", / / - asgn_regex "C.1.2", /[\u200b\p{Zs}&&[^ ]]/ - asgn_regex "C.2.1", /[\x00-\x1f\x7f]/ - # C.2.2 is a union: - # Cc + Cf (as defined by Unicode 3.2) + Zl + Zp + 0xfffc - # - any codepoints covered by C.2.1 or C.8 or C.9 - # - # But modern Unicode properties are significantly different, so it's better - # to just load the table definition. - asgn_table "C.2.2" - asgn_regex "C.3", /\p{private use}/ - asgn_regex "C.4", /\p{noncharacter code point}/ - asgn_regex "C.5", /\p{surrogate}/ - asgn_regex "C.6", /[\p{in specials}&&\p{AGE=3.2}&&\p{^NChar}]/ - asgn_regex "C.7", /[\p{in ideographic description characters}&&\p{AGE=3.2}]/ - # C.8 is a union of \p{Bidi Control} and Unicode 3.2 properties. But those properties - # have changed for modern Unicode, and thus for modern ruby's regexp - # character properties. It's better to just load the table definition. - asgn_table "C.8" - asgn_regex "C.9", /[\p{in Tags}&&\p{AGE=3.2}]/ - # Unfortunately, ruby doesn't (currently) support /[\p{Bidi - # Class=R}\p{bc=AL}]/. On the other hand, StringPrep (based on Unicode 3.2) - # might not be a good match for the modern (14.0) property value anyway. - asgn_table "D.1" - asgn_table "D.1", negate: true # used by BIDI_FAILS_REQ3 - asgn_table "D.2" - @asgn_regexps - end - - def regex_str(*names, negate: false) - "%p.freeze" % regexp_for(*names, negate: negate) - end - - def asgn_table(name, negate: false) - asgn_regex(name, regexp_for(name, negate: negate), negate: negate) - end - - def asgn_mapping(name, replacement = to_map(tables[name])) - cname = name.tr(?., ?_).upcase - "# Replacements for %s\n%s%s = %p.freeze" % [ - "IN_#{name}", " " * 2, "MAP_#{cname}", replacement, - ] - end - - def regexp_const_desc(name, negate: false) - if negate then "Matches the negation of the %s table" % [name] - else %q{%s \\StringPrep\\[\\"%s\\"]} % [titles.fetch(name), name] - end - end - - def regexp_const_name(table_name, negate: false) - "IN_%s%s" % [table_name.tr(".", "_"), negate ? "_NEGATED" : ""] - end - - def asgn_regex(name, regexp, negate: false) - asgn_regexps[[name, negate]] = regexp - "# %s\n%s%s = %p.freeze" % [ - regexp_const_desc(name, negate: negate), " " * 4, - regexp_const_name(name, negate: negate), - regexp, - ] - end - - def bidi_R_AL ; regexp_for "D.1" end - def bidi_not_R_AL ; regexp_for "D.1", negate: true end - def bidi_L ; regexp_for "D.2" end - - def bidi_fails_req2 - Regexp.union( - /#{bidi_R_AL}.*?#{bidi_L}/mu, # RandALCat followed by LCat - /#{bidi_L}.*?#{bidi_R_AL}/mu, # RandALCat preceded by LCat - ) - end - - def bidi_fails_req3 - # contains RandALCat: - Regexp.union( - /\A#{bidi_not_R_AL}.*?#{bidi_R_AL}/mu, # but doesn't start with RandALCat - /#{bidi_R_AL}.*?#{bidi_not_R_AL}\z/mu, # but doesn't end with RandALCat - ) - end - - def bidi_failure_regexp - Regexp.union(bidi_fails_req2, bidi_fails_req3) - end - - SASL_TABLES_PROHIBITED = %w[ - C.1.2 C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.7 C.8 C.9 - ].freeze - - SASL_TABLES_PROHIBITED_STORED = %w[ - A.1 C.1.2 C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.7 C.8 C.9 - ].freeze - -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/Gemfile ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/Gemfile --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/Gemfile 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/Gemfile 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec + +gem "rake" +gem "rdoc" +gem "test-unit" +gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core" + +gem "benchmark-driver" diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/LICENSE.txt ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/LICENSE.txt --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/LICENSE.txt 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,84 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +------------------------------------------------------------------------- + +This software includes documentation which has been copied from the relevant +RFCs. The copied documentation is covered by the following licenses: + +RFC 3501 (Editor: M. Crispin) +Full Copyright Statement + + Copyright (C) The Internet Society (2003). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. v This + document and the information contained herein is provided on an "AS + IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK + FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT + LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL + NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY + OR FITNESS FOR A PARTICULAR PURPOSE. + + +RFC9051 (Editors: A. Melnikov, B. Leiba) +Copyright Notice + + Copyright (c) 2021 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (https://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + This document may contain material from IETF Documents or IETF + Contributions published or made publicly available before November + 10, 2008. The person(s) controlling the copyright in some of this + material may not have granted the IETF Trust the right to allow + modifications of such material outside the IETF Standards Process. + Without obtaining an adequate license from the person(s) controlling + the copyright in such materials, this document may not be modified + outside the IETF Standards Process, and derivative works of it may + not be created outside the IETF Standards Process, except to format + it for publication as an RFC or to translate it into languages other + than English. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/README.md ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/README.md --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/README.md 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/README.md 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,71 @@ +# Net::IMAP + +Net::IMAP implements Internet Message Access Protocol (IMAP) client +functionality. The protocol is described in [IMAP](https://tools.ietf.org/html/rfc3501). + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'net-imap' +``` + +And then execute: + + $ bundle install + +Or install it yourself as: + + $ gem install net-imap + +## Usage + +### Connect with TLS to port 993 + +```ruby +imap = Net::IMAP.new('mail.example.com', ssl: true) +imap.port => 993 +imap.tls_verified? => true +case imap.greeting.name +in /OK/i + # The client is connected in the "Not Authenticated" state. + imap.authenticate("PLAIN", "joe_user", "joes_password") +in /PREAUTH/i + # The client is connected in the "Authenticated" state. +end +``` + +### List sender and subject of all recent messages in the default mailbox + +```ruby +imap.examine('INBOX') +imap.search(["RECENT"]).each do |message_id| + envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] + puts "#{envelope.from[0].name}: \t#{envelope.subject}" +end +``` + +### Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03" + +```ruby +imap.select('Mail/sent-mail') +if not imap.list('Mail/', 'sent-apr03') + imap.create('Mail/sent-apr03') +end +imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id| + imap.copy(message_id, "Mail/sent-apr03") + imap.store(message_id, "+FLAGS", [:Deleted]) +end +imap.expunge +``` + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-imap. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/Rakefile ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/Rakefile --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/Rakefile 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/Rakefile 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "rake/testtask" +require "rake/clean" + +Rake::TestTask.new(:test) do |t| + t.libs << "test/lib" + t.ruby_opts << "-rhelper" + t.test_files = FileList["test/**/test_*.rb"] +end + +task :default => :test diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/docs/styles.css ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/docs/styles.css --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/docs/styles.css 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/docs/styles.css 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,24 @@ +/* this is a work in progress. :) */ + +main .method-header { + background: rgba(27,31,35,0.05); + border: 1px solid #6C8C22; + padding: 0.5em; + border-radius: 4px; + /* padding: 0 0.5em; */ + /* border-width: 0 1px; */ + /* border-color: #6C8C22; */ + /* border-style: solid; */ +} + +main .method-description, main .aliases { + padding-left: 1em; +} + +body { + /* + * The default (300) can be too low contrast. Also, many fonts don't + * distinguish between 300->400, so ... had no effect. + */ + font-weight: 400; +} diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/authenticators.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/authenticators.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/authenticators.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/authenticators.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Backward compatible delegators from Net::IMAP to Net::IMAP::SASL. +module Net::IMAP::Authenticators + + # Deprecated. Use Net::IMAP::SASL.add_authenticator instead. + def add_authenticator(...) + warn( + "%s.%s is deprecated. Use %s.%s instead." % [ + Net::IMAP, __method__, Net::IMAP::SASL, __method__ + ], + uplevel: 1 + ) + Net::IMAP::SASL.add_authenticator(...) + end + + # Deprecated. Use Net::IMAP::SASL.authenticator instead. + def authenticator(...) + warn( + "%s.%s is deprecated. Use %s.%s instead." % [ + Net::IMAP, __method__, Net::IMAP::SASL, __method__ + ], + uplevel: 1 + ) + Net::IMAP::SASL.authenticator(...) + end + + Net::IMAP.extend self +end + +class Net::IMAP + PlainAuthenticator = SASL::PlainAuthenticator # :nodoc: + deprecate_constant :PlainAuthenticator + + XOauth2Authenticator = SASL::XOAuth2Authenticator # :nodoc: + deprecate_constant :XOauth2Authenticator +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/command_data.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/command_data.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/command_data.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/command_data.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,307 @@ +# frozen_string_literal: true + +require "date" + +require_relative "errors" + +module Net + class IMAP < Protocol + + private + + def validate_data(data) + case data + when nil + when String + when Integer + NumValidator.ensure_number(data) + when Array + if data[0] == 'CHANGEDSINCE' + NumValidator.ensure_mod_sequence_value(data[1]) + else + data.each do |i| + validate_data(i) + end + end + when Time, Date, DateTime + when Symbol + else + data.validate + end + end + + def send_data(data, tag = nil) + case data + when nil + put_string("NIL") + when String + send_string_data(data, tag) + when Integer + send_number_data(data) + when Array + send_list_data(data, tag) + when Date + send_date_data(data) + when Time, DateTime + send_time_data(data) + when Symbol + send_symbol_data(data) + else + data.send_data(self, tag) + end + end + + def send_string_data(str, tag = nil) + if str.empty? + put_string('""') + elsif str.match?(/[\r\n]/n) + # literal, because multiline + send_literal(str, tag) + elsif !str.ascii_only? + if @utf8_strings + # quoted string + send_quoted_string(str) + else + # literal, because of non-ASCII bytes + send_literal(str, tag) + end + elsif str.match?(/[(){ \x00-\x1f\x7f%*"\\]/n) + # quoted string + send_quoted_string(str) + else + put_string(str) + end + end + + def send_quoted_string(str) + put_string('"' + str.gsub(/["\\]/, "\\\\\\&") + '"') + end + + def send_literal(str, tag = nil) + synchronize do + put_string("{" + str.bytesize.to_s + "}" + CRLF) + @continued_command_tag = tag + @continuation_request_exception = nil + begin + @continuation_request_arrival.wait + e = @continuation_request_exception || @exception + raise e if e + put_string(str) + ensure + @continued_command_tag = nil + @continuation_request_exception = nil + end + end + end + + def send_number_data(num) + put_string(num.to_s) + end + + def send_list_data(list, tag = nil) + put_string("(") + first = true + list.each do |i| + if first + first = false + else + put_string(" ") + end + send_data(i, tag) + end + put_string(")") + end + + def send_date_data(date) put_string Net::IMAP.encode_date(date) end + def send_time_data(time) put_string Net::IMAP.encode_time(time) end + + def send_symbol_data(symbol) + put_string("\\" + symbol.to_s) + end + + class RawData # :nodoc: + def send_data(imap, tag) + imap.__send__(:put_string, @data) + end + + def validate + end + + private + + def initialize(data) + @data = data + end + end + + class Atom # :nodoc: + def send_data(imap, tag) + imap.__send__(:put_string, @data) + end + + def validate + end + + private + + def initialize(data) + @data = data + end + end + + class QuotedString # :nodoc: + def send_data(imap, tag) + imap.__send__(:send_quoted_string, @data) + end + + def validate + end + + private + + def initialize(data) + @data = data + end + end + + class Literal # :nodoc: + def send_data(imap, tag) + imap.__send__(:send_literal, @data, tag) + end + + def validate + end + + private + + def initialize(data) + @data = data + end + end + + class MessageSet # :nodoc: + def send_data(imap, tag) + imap.__send__(:put_string, format_internal(@data)) + end + + def validate + validate_internal(@data) + end + + private + + def initialize(data) + @data = data + end + + def format_internal(data) + case data + when "*" + return data + when Integer + if data == -1 + return "*" + else + return data.to_s + end + when Range + return format_internal(data.first) + + ":" + format_internal(data.last) + when Array + return data.collect {|i| format_internal(i)}.join(",") + when ThreadMember + return data.seqno.to_s + + ":" + data.children.collect {|i| format_internal(i).join(",")} + end + end + + def validate_internal(data) + case data + when "*" + when Integer + NumValidator.ensure_nz_number(data) + when Range + when Array + data.each do |i| + validate_internal(i) + end + when ThreadMember + data.children.each do |i| + validate_internal(i) + end + else + raise DataFormatError, data.inspect + end + end + end + + class ClientID # :nodoc: + + def send_data(imap, tag) + imap.__send__(:send_data, format_internal(@data), tag) + end + + def validate + validate_internal(@data) + end + + private + + def initialize(data) + @data = data + end + + def validate_internal(client_id) + client_id.to_h.each do |k,v| + unless StringFormatter.valid_string?(k) + raise DataFormatError, client_id.inspect + end + end + rescue NoMethodError, TypeError # to_h failed + raise DataFormatError, client_id.inspect + end + + def format_internal(client_id) + return nil if client_id.nil? + client_id.to_h.flat_map {|k,v| + [StringFormatter.string(k), StringFormatter.nstring(v)] + } + end + + end + + module StringFormatter + + LITERAL_REGEX = /[\x80-\xff\r\n]/n + + module_function + + # Allows symbols in addition to strings + def valid_string?(str) + str.is_a?(Symbol) || str.respond_to?(:to_str) + end + + # Allows nil, symbols, and strings + def valid_nstring?(str) + str.nil? || valid_string?(str) + end + + # coerces using +to_s+ + def string(str) + str = str.to_s + if str =~ LITERAL_REGEX + Literal.new(str) + else + QuotedString.new(str) + end + end + + # coerces non-nil using +to_s+ + def nstring(str) + str.nil? ? nil : string(str) + end + + end + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/data_encoding.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/data_encoding.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/data_encoding.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/data_encoding.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +require "date" +require "time" + +require_relative "errors" + +module Net + class IMAP < Protocol + + # strftime/strptime format for an IMAP4 +date+, excluding optional dquotes. + # Use via the encode_date and decode_date methods. + # + # date = date-text / DQUOTE date-text DQUOTE + # date-text = date-day "-" date-month "-" date-year + # + # date-day = 1*2DIGIT + # ; Day of month + # date-month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / + # "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" + # date-year = 4DIGIT + STRFDATE = "%d-%b-%Y" + + # strftime/strptime format for an IMAP4 +date-time+, including dquotes. + # See the encode_datetime and decode_datetime methods. + # + # date-time = DQUOTE date-day-fixed "-" date-month "-" date-year + # SP time SP zone DQUOTE + # + # date-day-fixed = (SP DIGIT) / 2DIGIT + # ; Fixed-format version of date-day + # date-month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / + # "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" + # date-year = 4DIGIT + # time = 2DIGIT ":" 2DIGIT ":" 2DIGIT + # ; Hours minutes seconds + # zone = ("+" / "-") 4DIGIT + # ; Signed four-digit value of hhmm representing + # ; hours and minutes east of Greenwich (that is, + # ; the amount that the given time differs from + # ; Universal Time). Subtracting the timezone + # ; from the given time will give the UT form. + # ; The Universal Time zone is "+0000". + # + # Note that Time.strptime "%d" flexibly parses either space or zero + # padding. However, the DQUOTEs are *not* optional. + STRFTIME = '"%d-%b-%Y %H:%M:%S %z"' + + # Decode a string from modified UTF-7 format to UTF-8. + # + # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a + # slightly modified version of this to encode mailbox names + # containing non-ASCII characters; see [IMAP] section 5.1.3. + # + # Net::IMAP does _not_ automatically encode and decode + # mailbox names to and from UTF-7. + def self.decode_utf7(s) + return s.gsub(/&([A-Za-z0-9+,]+)?-/n) { + if base64 = $1 + (base64.tr(",", "/") + "===").unpack1("m").encode(Encoding::UTF_8, Encoding::UTF_16BE) + else + "&" + end + } + end + + # Encode a string from UTF-8 format to modified UTF-7. + def self.encode_utf7(s) + return s.gsub(/(&)|[^\x20-\x7e]+/) { + if $1 + "&-" + else + base64 = [$&.encode(Encoding::UTF_16BE)].pack("m0") + "&" + base64.delete("=").tr("/", ",") + "-" + end + }.force_encoding("ASCII-8BIT") + end + + # Formats +time+ as an IMAP4 date. + def self.encode_date(date) + date.to_date.strftime STRFDATE + end + + # :call-seq: decode_date(string) -> Date + # + # Decodes +string+ as an IMAP formatted "date". + # + # Double quotes are optional. Day of month may be padded with zero or + # space. See STRFDATE. + def self.decode_date(string) + string = string.delete_prefix('"').delete_suffix('"') + Date.strptime(string, STRFDATE) + end + + # :call-seq: encode_datetime(time) -> string + # + # Formats +time+ as an IMAP4 date-time. + def self.encode_datetime(time) + time.to_datetime.strftime STRFTIME + end + + # :call-seq: decode_datetime(string) -> DateTime + # + # Decodes +string+ as an IMAP4 formatted "date-time". + # + # NOTE: Although double-quotes are not optional in the IMAP grammar, + # Net::IMAP currently parses "date-time" values as "quoted" strings and this + # removes the quotation marks. To be useful for strings which have already + # been parsed as a quoted string, this method makes double-quotes optional. + # + # See STRFTIME. + def self.decode_datetime(string) + unless string.start_with?(?") && string.end_with?(?") + string = '"%s"' % [string] + end + DateTime.strptime(string, STRFTIME) + end + + # :call-seq: decode_time(string) -> Time + # + # Decodes +string+ as an IMAP4 formatted "date-time". + # + # Same as +decode_datetime+, but returning a Time instead. + def self.decode_time(string) + unless string.start_with?(?") && string.end_with?(?") + string = '"%s"' % [string] + end + Time.strptime(string, STRFTIME) + end + + class << self + alias encode_time encode_datetime + alias format_date encode_date + alias format_time encode_time + alias parse_date decode_date + alias parse_datetime decode_datetime + alias parse_time decode_time + + # alias format_datetime encode_datetime # n.b. this is overridden below... + end + + # DEPRECATED:: The original version returned incorrectly formatted strings. + # Strings returned by encode_datetime or format_time use the + # correct IMAP4rev1 syntax for "date-time". + # + # This invalid format has been temporarily retained for backward + # compatibility. A future release will change this method to return the + # correct format. + def self.format_datetime(time) + warn("#{self}.format_datetime incorrectly formats IMAP date-time. " \ + "Convert to #{self}.encode_datetime or #{self}.format_time instead.", + uplevel: 1, category: :deprecated) + time.strftime("%d-%b-%Y %H:%M %z") + end + + # Common validators of number and nz_number types + module NumValidator # :nodoc + module_function + + # Check is passed argument valid 'number' in RFC 3501 terminology + def valid_number?(num) + # [RFC 3501] + # number = 1*DIGIT + # ; Unsigned 32-bit integer + # ; (0 <= n < 4,294,967,296) + num >= 0 && num < 4294967296 + end + + # Check is passed argument valid 'nz_number' in RFC 3501 terminology + def valid_nz_number?(num) + # [RFC 3501] + # nz-number = digit-nz *DIGIT + # ; Non-zero unsigned 32-bit integer + # ; (0 < n < 4,294,967,296) + num != 0 && valid_number?(num) + end + + # Check is passed argument valid 'mod_sequence_value' in RFC 4551 terminology + def valid_mod_sequence_value?(num) + # mod-sequence-value = 1*DIGIT + # ; Positive unsigned 64-bit integer + # ; (mod-sequence) + # ; (1 <= n < 18,446,744,073,709,551,615) + num >= 1 && num < 18446744073709551615 + end + + # Ensure argument is 'number' or raise DataFormatError + def ensure_number(num) + return if valid_number?(num) + + msg = "number must be unsigned 32-bit integer: #{num}" + raise DataFormatError, msg + end + + # Ensure argument is 'nz_number' or raise DataFormatError + def ensure_nz_number(num) + return if valid_nz_number?(num) + + msg = "nz_number must be non-zero unsigned 32-bit integer: #{num}" + raise DataFormatError, msg + end + + # Ensure argument is 'mod_sequence_value' or raise DataFormatError + def ensure_mod_sequence_value(num) + return if valid_mod_sequence_value?(num) + + msg = "mod_sequence_value must be unsigned 64-bit integer: #{num}" + raise DataFormatError, msg + end + + end + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/deprecated_client_options.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/deprecated_client_options.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/deprecated_client_options.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/deprecated_client_options.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + + # This module handles deprecated arguments to various Net::IMAP methods. + module DeprecatedClientOptions + + # :call-seq: + # Net::IMAP.new(host, **options) # standard keyword options + # Net::IMAP.new(host, options) # obsolete hash options + # Net::IMAP.new(host, port) # obsolete port argument + # Net::IMAP.new(host, port, usessl, certs = nil, verify = true) # deprecated SSL arguments + # + # Translates Net::IMAP.new arguments for backward compatibility. + # + # ==== Obsolete arguments + # + # Using obsolete arguments does not a warning. Obsolete arguments will be + # deprecated by a future release. + # + # If a second positional argument is given and it is a hash (or is + # convertable via +#to_hash+), it is converted to keyword arguments. + # + # # Obsolete: + # Net::IMAP.new("imap.example.com", options_hash) + # # Use instead: + # Net::IMAP.new("imap.example.com", **options_hash) + # + # If a second positional argument is given and it is not a hash, it is + # converted to the +port+ keyword argument. + # # Obsolete: + # Net::IMAP.new("imap.example.com", 114433) + # # Use instead: + # Net::IMAP.new("imap.example.com", port: 114433) + # + # ==== Deprecated arguments + # + # Using deprecated arguments prints a warning. Convert to keyword + # arguments to avoid the warning. Deprecated arguments will be removed in + # a future release. + # + # If +usessl+ is false, +certs+, and +verify+ are ignored. When it true, + # all three arguments are converted to the +ssl+ keyword argument. + # Without +certs+ or +verify+, it is converted to ssl: true. + # # DEPRECATED: + # Net::IMAP.new("imap.example.com", nil, true) # => prints a warning + # # Use instead: + # Net::IMAP.new("imap.example.com", ssl: true) + # + # When +certs+ is a path to a directory, it is converted to ca_path: + # certs. + # # DEPRECATED: + # Net::IMAP.new("imap.example.com", nil, true, "/path/to/certs") # => prints a warning + # # Use instead: + # Net::IMAP.new("imap.example.com", ssl: {ca_path: "/path/to/certs"}) + # + # When +certs+ is a path to a file, it is converted to ca_file: + # certs. + # # DEPRECATED: + # Net::IMAP.new("imap.example.com", nil, true, "/path/to/cert.pem") # => prints a warning + # # Use instead: + # Net::IMAP.new("imap.example.com", ssl: {ca_file: "/path/to/cert.pem"}) + # + # When +verify+ is +false+, it is converted to verify_mode: + # OpenSSL::SSL::VERIFY_NONE. + # # DEPRECATED: + # Net::IMAP.new("imap.example.com", nil, true, nil, false) # => prints a warning + # # Use instead: + # Net::IMAP.new("imap.example.com", ssl: {verify_mode: OpenSSL::SSL::VERIFY_NONE}) + # + def initialize(host, port_or_options = nil, *deprecated, **options) + if port_or_options.nil? && deprecated.empty? + super host, **options + elsif options.any? + # Net::IMAP.new(host, *__invalid__, **options) + raise ArgumentError, "Do not combine deprecated and keyword arguments" + elsif port_or_options.respond_to?(:to_hash) and deprecated.any? + # Net::IMAP.new(host, options, *__invalid__) + raise ArgumentError, "Do not use deprecated SSL params with options hash" + elsif port_or_options.respond_to?(:to_hash) + super host, **Hash.try_convert(port_or_options) + elsif deprecated.empty? + super host, port: port_or_options + elsif deprecated.shift + warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1 + super host, port: port_or_options, ssl: create_ssl_params(*deprecated) + else + warn "DEPRECATED: Call Net::IMAP.new with keyword options", uplevel: 1 + super host, port: port_or_options, ssl: false + end + end + + # :call-seq: + # starttls(**options) # standard + # starttls(options = {}) # obsolete + # starttls(certs = nil, verify = true) # deprecated + # + # Translates Net::IMAP#starttls arguments for backward compatibility. + # + # Support for +certs+ and +verify+ will be dropped in a future release. + # + # See ::new for interpretation of +certs+ and +verify+. + def starttls(*deprecated, **options) + if deprecated.empty? + super(**options) + elsif options.any? + # starttls(*__invalid__, **options) + raise ArgumentError, "Do not combine deprecated and keyword options" + elsif deprecated.first.respond_to?(:to_hash) && deprecated.length > 1 + # starttls(*__invalid__, **options) + raise ArgumentError, "Do not use deprecated verify param with options hash" + elsif deprecated.first.respond_to?(:to_hash) + super(**Hash.try_convert(deprecated.first)) + else + warn "DEPRECATED: Call Net::IMAP#starttls with keyword options", uplevel: 1 + super(**create_ssl_params(*deprecated)) + end + end + + private + + def create_ssl_params(certs = nil, verify = true) + params = {} + if certs + if File.file?(certs) + params[:ca_file] = certs + elsif File.directory?(certs) + params[:ca_path] = certs + end + end + params[:verify_mode] = + verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE + params + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/errors.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/errors.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/errors.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/errors.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + + # Superclass of IMAP errors. + class Error < StandardError + end + + # Error raised when data is in the incorrect format. + class DataFormatError < Error + end + + # Error raised when a response from the server is non-parseable. + class ResponseParseError < Error + end + + # Superclass of all errors used to encapsulate "fail" responses + # from the server. + class ResponseError < Error + + # The response that caused this error + attr_accessor :response + + def initialize(response) + @response = response + + super @response.data.text + end + + end + + # Error raised upon a "NO" response from the server, indicating + # that the client command could not be completed successfully. + class NoResponseError < ResponseError + end + + # Error raised upon a "BAD" response from the server, indicating + # that the client command violated the IMAP protocol, or an internal + # server failure has occurred. + class BadResponseError < ResponseError + end + + # Error raised upon a "BYE" response from the server, indicating + # that the client is not being allowed to login, or has been timed + # out due to inactivity. + class ByeResponseError < ResponseError + end + + # Error raised when the server sends an invalid response. + # + # This is different from UnknownResponseError: the response has been + # rejected. Although it may be parsable, the server is forbidden from + # sending it in the current context. The client should automatically + # disconnect, abruptly (without logout). + # + # Note that InvalidResponseError does not inherit from ResponseError: it + # can be raised before the response is fully parsed. A related + # ResponseParseError or ResponseError may be the #cause. + class InvalidResponseError < Error + end + + # Error raised upon an unknown response from the server. + # + # This is different from InvalidResponseError: the response may be a + # valid extension response and the server may be allowed to send it in + # this context, but Net::IMAP either does not know how to parse it or + # how to handle it. This could result from enabling unknown or + # unhandled extensions. The connection may still be usable, + # but—depending on context—it may be prudent to disconnect. + class UnknownResponseError < ResponseError + end + + RESPONSE_ERRORS = Hash.new(ResponseError) # :nodoc: + RESPONSE_ERRORS["NO"] = NoResponseError + RESPONSE_ERRORS["BAD"] = BadResponseError + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/fetch_data.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/fetch_data.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/fetch_data.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/fetch_data.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,518 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + + # Net::IMAP::FetchData represents the contents of a FETCH response. + # Net::IMAP#fetch and Net::IMAP#uid_fetch both return an array of + # FetchData objects. + # + # === Fetch attributes + # + # See {[IMAP4rev1 §7.4.2]}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] + # and {[IMAP4rev2 §7.5.2]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2] + # for a full description of the standard fetch response data items, and + # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. + # + # ==== Static fetch data items + # + # Most message attributes are static, and must never change for a given + # (server, account, mailbox, UIDVALIDITY, UID) tuple. + # + # The static fetch data items defined by both + # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and + # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] are: + # + # * "UID" --- See #uid. + # * "BODY" --- See #body. + # * "BODY[#{section_spec}]", + # "BODY[#{section_spec}]<#{offset}>" --- See #message, + # #part, #header, #header_fields, #header_fields_not, #mime, and #text. + # * "BODYSTRUCTURE" --- See #bodystructure. + # * "ENVELOPE" --- See #envelope. + # * "INTERNALDATE" --- See #internaldate. + # * "RFC822.SIZE" --- See #rfc822_size. + # + # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] adds the + # additional fetch items from the +BINARY+ extension + # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html]: + # + # * "BINARY[#{part}]", + # "BINARY[#{part}]<#{offset}>" -- See #binary. + # * "BINARY.SIZE[#{part}]" -- See #binary_size. + # + # Several static message attributes in + # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] are obsolete and + # been removed from + # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]: + # + # * "RFC822" --- See #rfc822 or replace with + # "BODY[]" and #message. + # * "RFC822.HEADER" --- See #rfc822_header or replace with + # "BODY[HEADER]" and #header. + # * "RFC822.TEXT" --- See #rfc822_text or replace with + # "BODY[TEXT]" and #text. + # + # Net::IMAP supports static attributes defined by the following extensions: + # * +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html] + # * "EMAILID" --- See #emailid. + # * "THREADID" --- See #threadid. + # + # * +X-GM-EXT-1+ {[non-standard Gmail + # extension]}[https://developers.google.com/gmail/imap/imap-extensions] + # * "X-GM-MSGID" --- unique message ID. Access via #attr. + # * "X-GM-THRID" --- Thread ID. Access via #attr. + # + # [Note:] + # >>> + # Additional static fields are defined in other \IMAP extensions, but + # Net::IMAP can't parse them yet. + # + # ==== Dynamic message attributes + # + # Some message attributes can be dynamically changed, for example using the + # {STORE command}[rdoc-ref:Net::IMAP#store]. + # + # The only dynamic message attribute defined by + # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and + # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] is: + # + # * "FLAGS" --- See #flags. + # + # Net::IMAP supports dynamic attributes defined by the following extensions: + # + # * +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]: + # * "MODSEQ" --- See #modseq. + # * +X-GM-EXT-1+ {[non-standard Gmail + # extension]}[https://developers.google.com/gmail/imap/imap-extensions] + # * "X-GM-LABELS" --- Gmail labels. Access via #attr. + # + # [Note:] + # >>> + # Additional dynamic fields are defined in other \IMAP extensions, but + # Net::IMAP can't parse them yet. + # + # === Implicitly setting \Seen and using +PEEK+ + # + # Unless the mailbox is has been opened as read-only, fetching + # BODY[#{section}] or BINARY[#{section}] + # will implicitly set the \Seen flag. To avoid this, fetch using + # BODY.PEEK[#{section}] or BINARY.PEEK[#{section}] + # instead. + # + # Note that the data will always be _returned_ without ".PEEK", in + # BODY[#{specifier}] or BINARY[#{section}]. + # + class FetchData < Struct.new(:seqno, :attr) + ## + # method: seqno + # :call-seq: seqno -> Integer + # + # The message sequence number. + # + # [Note] + # This is never the unique identifier (UID), not even for the + # Net::IMAP#uid_fetch result. The UID is available from #uid, if it was + # returned. + + ## + # method: attr + # :call-seq: attr -> hash + # + # Each key specifies a message attribute, and the value is the + # corresponding data item. Standard data items have corresponding + # accessor methods. The definitions of each attribute type is documented + # on its accessor. + # + # >>> + # *Note:* #seqno is not a message attribute. + + # :call-seq: attr_upcase -> hash + # + # A transformation of #attr, with all the keys converted to upper case. + # + # Header field names are case-preserved but not case-sensitive, so this is + # used by #header_fields and #header_fields_not. + def attr_upcase; attr.transform_keys(&:upcase) end + + # :call-seq: + # body -> body structure or nil + # + # Returns an alternate form of #bodystructure, without any extension data. + # + # This is the same as getting the value for "BODY" from #attr. + # + # [Note] + # Use #message, #part, #header, #header_fields, #header_fields_not, + # #text, or #mime to retrieve BODY[#{section_spec}] attributes. + def body; attr["BODY"] end + + # :call-seq: + # message(offset: bytes) -> string or nil + # + # The RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html] + # expression of the entire message, as a string. + # + # See #part for a description of +offset+. + # + # RFC5322 messages can be parsed using the "mail" gem. + # + # This is the same as getting the value for "BODY[]" or + # "BODY[]<#{offset}>" from #attr. + # + # See also: #header, #text, and #mime. + def message(offset: nil) attr[body_section_attr(offset: offset)] end + + # :call-seq: + # part(*part_nums, offset: bytes) -> string or nil + # + # The string representation of a particular MIME part. + # + # +part_nums+ forms a path of MIME part numbers, counting up from +1+, + # which may specify an arbitrarily nested part, similarly to Array#dig. + # Messages that don't use MIME, or MIME messages that are not multipart + # and don't hold an encapsulated message, only have part +1+. + # + # If a zero-based +offset+ is given, the returned string is a substring of + # the entire contents, starting at that origin octet. This means that + # BODY[]<0> MAY be truncated, but BODY[] is never + # truncated. + # + # This is the same as getting the value of + # "BODY[#{part_nums.join(".")}]" or + # "BODY[#{part_nums.join(".")}]<#{offset}>" from #attr. + # + # See also: #message, #header, #text, and #mime. + def part(index, *subparts, offset: nil) + attr[body_section_attr([index, *subparts], offset: offset)] + end + + # :call-seq: + # header(*part_nums, offset: nil) -> string or nil + # header(*part_nums, fields: names, offset: nil) -> string or nil + # header(*part_nums, except: names, offset: nil) -> string or nil + # + # The {[RFC5322]}[https://www.rfc-editor.org/rfc/rfc5322.html] header of a + # message or of an encapsulated + # {[MIME-IMT]}[https://www.rfc-editor.org/rfc/rfc2046.html] + # MESSAGE/RFC822 or MESSAGE/GLOBAL message. + # + # Headers can be parsed using the "mail" gem. + # + # See #part for a description of +part_nums+ and +offset+. + # + # ==== Without +fields+ or +except+ + # This is the same as getting the value from #attr for one of: + # * BODY[HEADER] + # * BODY[HEADER]<#{offset}> + # * BODY[#{part_nums.join "."}.HEADER]" + # * BODY[#{part_nums.join "."}.HEADER]<#{offset}>" + # + # ==== With +fields+ + # When +fields+ is sent, returns a subset of the header which contains + # only the header fields that match one of the names in the list. + # + # This is the same as getting the value from #attr_upcase for one of: + # * BODY[HEADER.FIELDS (#{names.join " "})] + # * BODY[HEADER.FIELDS (#{names.join " "})]<#{offset}> + # * BODY[#{part_nums.join "."}.HEADER.FIELDS (#{names.join " "})] + # * BODY[#{part_nums.join "."}.HEADER.FIELDS (#{names.join " "})]<#{offset}> + # + # See also: #header_fields + # + # ==== With +except+ + # When +except+ is sent, returns a subset of the header which contains + # only the header fields that do _not_ match one of the names in the list. + # + # This is the same as getting the value from #attr_upcase for one of: + # * BODY[HEADER.FIELDS.NOT (#{names.join " "})] + # * BODY[HEADER.FIELDS.NOT (#{names.join " "})]<#{offset}> + # * BODY[#{part_nums.join "."}.HEADER.FIELDS.NOT (#{names.join " "})] + # * BODY[#{part_nums.join "."}.HEADER.FIELDS.NOT (#{names.join " "})]<#{offset}> + # + # See also: #header_fields_not + def header(*part_nums, fields: nil, except: nil, offset: nil) + fields && except and + raise ArgumentError, "conflicting 'fields' and 'except' arguments" + if fields + text = "HEADER.FIELDS (%s)" % [fields.join(" ").upcase] + attr_upcase[body_section_attr(part_nums, text, offset: offset)] + elsif except + text = "HEADER.FIELDS.NOT (%s)" % [except.join(" ").upcase] + attr_upcase[body_section_attr(part_nums, text, offset: offset)] + else + attr[body_section_attr(part_nums, "HEADER", offset: offset)] + end + end + + # :call-seq: + # header_fields(*names, part: [], offset: nil) -> string or nil + # + # The result from #header when called with fields: names. + def header_fields(first, *rest, part: [], offset: nil) + header(*part, fields: [first, *rest], offset: offset) + end + + # :call-seq: + # header_fields_not(*names, part: [], offset: nil) -> string or nil + # + # The result from #header when called with except: names. + def header_fields_not(first, *rest, part: [], offset: nil) + header(*part, except: [first, *rest], offset: offset) + end + + # :call-seq: + # mime(*part_nums) -> string or nil + # mime(*part_nums, offset: bytes) -> string or nil + # + # The {[MIME-IMB]}[https://www.rfc-editor.org/rfc/rfc2045.html] header for + # a message part, if it was fetched. + # + # See #part for a description of +part_nums+ and +offset+. + # + # This is the same as getting the value for + # "BODY[#{part_nums}.MIME]" or + # "BODY[#{part_nums}.MIME]<#{offset}>" from #attr. + # + # See also: #message, #header, and #text. + def mime(part, *subparts, offset: nil) + attr[body_section_attr([part, *subparts], "MIME", offset: offset)] + end + + # :call-seq: + # text(*part_nums) -> string or nil + # text(*part_nums, offset: bytes) -> string or nil + # + # The text body of a message or a message part, if it was fetched, + # omitting the {[RFC5322]}[https://www.rfc-editor.org/rfc/rfc5322.html] + # header. + # + # See #part for a description of +part_nums+ and +offset+. + # + # This is the same as getting the value from #attr for one of: + # * "BODY[TEXT]", + # * "BODY[TEXT]<#{offset}>", + # * "BODY[#{section}.TEXT]", or + # * "BODY[#{section}.TEXT]<#{offset}>". + # + # See also: #message, #header, and #mime. + def text(*part, offset: nil) + attr[body_section_attr(part, "TEXT", offset: offset)] + end + + # :call-seq: + # bodystructure -> BodyStructure struct or nil + # + # A BodyStructure object that describes the message, if it was fetched. + # + # This is the same as getting the value for "BODYSTRUCTURE" from + # #attr. + def bodystructure; attr["BODYSTRUCTURE"] end + alias body_structure bodystructure + + # :call-seq: envelope -> Envelope or nil + # + # An Envelope object that describes the envelope structure of a message. + # See the documentation for Envelope for a description of the envelope + # structure attributes. + # + # This is the same as getting the value for "ENVELOPE" from + # #attr. + def envelope; attr["ENVELOPE"] end + + # :call-seq: flags -> array of Symbols and Strings + # + # A array of flags that are set for this message. System flags are + # symbols that have been capitalized by String#capitalize. Keyword flags + # are strings and their case is not changed. + # + # This is the same as getting the value for "FLAGS" from #attr. + # + # [Note] + # The +FLAGS+ field is dynamic, and can change for a uniquely identified + # message. + def flags; attr["FLAGS"] end + + # :call-seq: internaldate -> Time or nil + # + # The internal date and time of the message on the server. This is not + # the date and time in the [RFC5322[https://tools.ietf.org/html/rfc5322]] + # header, but rather a date and time which reflects when the message was + # received. + # + # This is similar to getting the value for "INTERNALDATE" from + # #attr. + # + # [Note] + # attr["INTERNALDATE"] returns a string, and this method + # returns a Time object. + def internaldate + attr["INTERNALDATE"]&.then { IMAP.decode_time _1 } + end + alias internal_date internaldate + + # :call-seq: rfc822 -> String + # + # Semantically equivalent to #message with no arguments. + # + # This is the same as getting the value for "RFC822" from #attr. + # + # [Note] + # +IMAP4rev2+ deprecates RFC822. + def rfc822; attr["RFC822"] end + + # :call-seq: rfc822_size -> Integer + # + # A number expressing the [RFC5322[https://tools.ietf.org/html/rfc5322]] + # size of the message. + # + # This is the same as getting the value for "RFC822.SIZE" from + # #attr. + # + # [Note] + # \IMAP was originally developed for the older + # RFC822[https://www.rfc-editor.org/rfc/rfc822.html] standard, and as a + # consequence several fetch items in \IMAP incorporate "RFC822" in their + # name. With the exception of +RFC822.SIZE+, there are more modern + # replacements; for example, the modern version of +RFC822.HEADER+ is + # BODY.PEEK[HEADER]. In all cases, "RFC822" should be + # interpreted as a reference to the updated + # RFC5322[https://www.rfc-editor.org/rfc/rfc5322.html] standard. + def rfc822_size; attr["RFC822.SIZE"] end + alias size rfc822_size + + # :call-seq: rfc822_header -> String + # + # Semantically equivalent to #header, with no arguments. + # + # This is the same as getting the value for "RFC822.HEADER" from #attr. + # + # [Note] + # +IMAP4rev2+ deprecates RFC822.HEADER. + def rfc822_header; attr["RFC822.HEADER"] end + + # :call-seq: rfc822_text -> String + # + # Semantically equivalent to #text, with no arguments. + # + # This is the same as getting the value for "RFC822.TEXT" from + # #attr. + # + # [Note] + # +IMAP4rev2+ deprecates RFC822.TEXT. + def rfc822_text; attr["RFC822.TEXT"] end + + # :call-seq: uid -> Integer + # + # A number expressing the unique identifier of the message. + # + # This is the same as getting the value for "UID" from #attr. + def uid; attr["UID"] end + + # :call-seq: + # binary(*part_nums, offset: nil) -> string or nil + # + # Returns the binary representation of a particular MIME part, which has + # already been decoded according to its Content-Transfer-Encoding. + # + # See #part for a description of +part_nums+ and +offset+. + # + # This is the same as getting the value of + # "BINARY[#{part_nums.join(".")}]" or + # "BINARY[#{part_nums.join(".")}]<#{offset}>" from #attr. + # + # The server must support either + # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] + # or the +BINARY+ extension + # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html]. + # + # See also: #binary_size, #mime + def binary(*part_nums, offset: nil) + attr[section_attr("BINARY", part_nums, offset: offset)] + end + + # :call-seq: + # binary_size(*part_nums) -> integer or nil + # + # Returns the decoded size of a particular MIME part (the size to expect + # in response to a BINARY fetch request). + # + # See #part for a description of +part_nums+. + # + # This is the same as getting the value of + # "BINARY.SIZE[#{part_nums.join(".")}]" from #attr. + # + # The server must support either + # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html] + # or the +BINARY+ extension + # {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516.html]. + # + # See also: #binary, #mime + def binary_size(*part_nums) + attr[section_attr("BINARY.SIZE", part_nums)] + end + + # :call-seq: modseq -> Integer + # + # The modification sequence number associated with this IMAP message. + # + # This is the same as getting the value for "MODSEQ" from #attr. + # + # The server must support the +CONDSTORE+ extension + # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. + # + # [Note] + # The +MODSEQ+ field is dynamic, and can change for a uniquely + # identified message. + def modseq; attr["MODSEQ"] end + + # :call-seq: emailid -> string or nil + # + # An ObjectID that uniquely identifies the immutable content of a single + # message. + # + # The server must return the same +EMAILID+ for both the source and + # destination messages after a COPY or MOVE command. However, it is + # possible for different messages with the same EMAILID to have different + # mutable attributes, such as flags. + # + # This is the same as getting the value for "EMAILID" from + # #attr. + # + # The server must support the +OBJECTID+ extension + # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. + def emailid; attr["EMAILID"] end + + # :call-seq: threadid -> string or nil + # + # An ObjectID that uniquely identifies a set of messages that the server + # believes should be grouped together. + # + # It is generally based on some combination of References, In-Reply-To, + # and Subject, but the exact implementation is left up to the server + # implementation. The server should return the same thread identifier for + # related messages, even if they are in different mailboxes. + # + # This is the same as getting the value for "THREADID" from + # #attr. + # + # The server must support the +OBJECTID+ extension + # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. + def threadid; attr["THREADID"] end + + private + + def body_section_attr(...) section_attr("BODY", ...) end + + def section_attr(attr, part = [], text = nil, offset: nil) + spec = Array(part).flatten.map { Integer(_1) } + spec << text if text + spec = spec.join(".") + if offset then "%s[%s]<%d>" % [attr, spec, Integer(offset)] + else "%s[%s]" % [attr, spec] + end + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/flags.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/flags.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/flags.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/flags.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + + # ------------------------------------------------------------------------- + # :section: System Flags + # + # A message has a list of zero or more named tokens, known as "flags", + # associated with it. A flag is set by its addition to this list and is + # cleared by its removal. There are two types of flags in + # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] and + # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]: flags and + # keywords. A flag of either type can be permanent or session-only. + # + # A "system flag" is a message flag name that is predefined in the \IMAP + # specifications and begins with "\". Net::IMAP returns all + # system flags as symbols, without the "\" prefix. + # + # The descriptions here were copied from {[RFC-9051 + # §2.3.2]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-2.3.2]. + # See also {[RFC-3501 + # §2.3.2]}[https://www.rfc-editor.org/rfc/rfc3501.html#section-2.3.2], + # which describes the flags message attribute semantics under + # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html]. + # ------------------------------------------------------------------------- + + ## + # Flag indicating a message has been read. + SEEN = :Seen + + # Flag indicating a message has been answered. + ANSWERED = :Answered + + # A message flag indicating a message has been flagged for special or urgent + # attention. + # + # Also a mailbox special use attribute, which indicates that this mailbox + # presents all messages marked in some way as "important". When this + # special use is supported, it is likely to represent a virtual mailbox + # collecting messages (from other mailboxes) that are marked with the + # "\Flagged" message flag. + FLAGGED = :Flagged + + # Flag indicating a message has been marked for deletion. This + # will occur when the mailbox is closed or expunged. + DELETED = :Deleted + + # Flag indicating a message is only a draft or work-in-progress version. + DRAFT = :Draft + + # Flag indicating that the message is "recent," meaning that this + # session is the first session in which the client has been notified + # of this message. + # + # This flag was defined by + # IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501.html] + # and is deprecated by + # IMAP4rev2[https://www.rfc-editor.org/rfc/rfc9051.html]. + RECENT = :Recent + + # ------------------------------------------------------------------------- + # :section: Basic Mailbox Attributes + # Mailbox name attributes will be returned in #list responses. Base + # attributes must be returned according to the server's capabilities. + # + # IMAP4 specifies that all mailbox name attributes, including future + # extensions, begin with "\". Net::IMAP returns all mailbox + # attributes as symbols, without the "\" prefix. + # + # Mailbox name attributes are not case-sensitive. The current + # implementation normalizes mailbox attribute case using + # String#capitalize, such as +:Noselect+ (not +:NoSelect+). The constants + # (such as NO_SELECT) can also be used for comparison. The contants have + # been defined both with and without underscores between words. + # + # The descriptions here were copied from {[RFC-9051 § + # 7.3.1]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.1]. + # + # Other mailbox name attributes can be found in the {IANA IMAP Mailbox Name + # Attributes registry}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]. + # ------------------------------------------------------------------------- + + ## + # The +\NonExistent+ attribute indicates that a mailbox name does not refer + # to an existing mailbox. Note that this attribute is not meaningful by + # itself, as mailbox names that match the canonical #list pattern but don't + # exist must not be returned unless one of the two conditions listed below + # is also satisfied: + # + # 1. The mailbox name also satisfies the selection criteria (for example, + # it is subscribed and the "SUBSCRIBED" selection option has been + # specified). + # + # 2. "RECURSIVEMATCH" has been specified, and the mailbox name has at least + # one descendant mailbox name that does not match the #list pattern and + # does match the selection criteria. + # + # In practice, this means that the +\NonExistent+ attribute is usually + # returned with one or more of +\Subscribed+, +\Remote+, +\HasChildren+, or + # the CHILDINFO extended data item. + # + # The client must treat the presence of the +\NonExistent+ attribute as if the + # +\NoSelect+ attribute was also sent by the server + NONEXISTENT = :Nonexistent + + # Mailbox attribute indicating it is not possible for any child levels of + # hierarchy to exist under this name; no child levels exist now and none can + # be created in the future children. + # + # The client must treat the presence of the +\NoInferiors+ attribute as if the + # +\HasNoChildren+ attribute was also sent by the server + NO_INFERIORS = :Noinferiors + + # Mailbox attribute indicating it is not possible to use this name as a + # selectable mailbox. + NO_SELECT = :Noselect + + # The presence of this attribute indicates that the mailbox has child + # mailboxes. A server SHOULD NOT set this attribute if there are child + # mailboxes and the user does not have permission to access any of them. In + # this case, +\HasNoChildren+ SHOULD be used. In many cases, however, a + # server may not be able to efficiently compute whether a user has access to + # any child mailboxes. Note that even though the +\HasChildren+ attribute + # for a mailbox must be correct at the time of processing the mailbox, a + # client must be prepared to deal with a situation when a mailbox is marked + # with the +\HasChildren+ attribute, but no child mailbox appears in the + # response to the #list command. This might happen, for example, due to child + # mailboxes being deleted or made inaccessible to the user (using access + # control) by another client before the server is able to list them. + # + # It is an error for the server to return both a +\HasChildren+ and a + # +\HasNoChildren+ attribute in the same #list response. A client that + # encounters a #list response with both +\HasChildren+ and +\HasNoChildren+ + # attributes present should act as if both are absent in the #list response. + HAS_CHILDREN = :Haschildren + + # The presence of this attribute indicates that the mailbox has NO child + # mailboxes that are accessible to the currently authenticated user. + # + # It is an error for the server to return both a +\HasChildren+ and a + # +\HasNoChildren+ attribute in the same #list response. A client that + # encounters a #list response with both +\HasChildren+ and +\HasNoChildren+ + # attributes present should act as if both are absent in the #list response. + # + # Note: the +\HasNoChildren+ attribute should not be confused with the + # +\NoInferiors+ attribute, which indicates that no child mailboxes exist + # now and none can be created in the future. + HAS_NO_CHILDREN = :Hasnochildren + + # The mailbox has been marked "interesting" by the server; the mailbox + # probably contains messages that have been added since the last time the + # mailbox was selected. + # + # If it is not feasible for the server to determine whether or not the + # mailbox is "interesting", the server SHOULD NOT send either +\Marked+ or + # +\Unmarked+. The server MUST NOT send more than one of +\Marked+, + # +\Unmarked+, and +\NoSelect+ for a single mailbox, and it MAY send none of + # these. + MARKED = :Marked + + # The mailbox does not contain any additional messages since the last time + # the mailbox was selected. + # + # If it is not feasible for the server to determine whether or not the + # mailbox is "interesting", the server SHOULD NOT send either +\Marked+ or + # +\Unmarked+. The server MUST NOT send more than one of +\Marked+, + # +\Unmarked+, and +\NoSelect+ for a single mailbox, and it MAY send none of + # these. + UNMARKED = :Unmarked + + # The mailbox name was subscribed to using the #subscribe command. + SUBSCRIBED = :Subscribed + + # The mailbox is a remote mailbox. + REMOTE = :Remove + + # Alias for NO_INFERIORS, to match the \IMAP spelling. + NOINFERIORS = NO_INFERIORS + # Alias for NO_SELECT, to match the \IMAP spelling. + NOSELECT = NO_SELECT + # Alias for HAS_CHILDREN, to match the \IMAP spelling. + HASCHILDREN = HAS_CHILDREN + # Alias for HAS_NO_CHILDREN, to match the \IMAP spelling. + HASNOCHILDREN = HAS_NO_CHILDREN + + # ------------------------------------------------------------------------- + # :section: Mailbox role attributes + # + # Mailbox name attributes will be returned in #list responses. In addition + # to the base mailbox name attributes defined above, an \IMAP server MAY + # also include any or all of the following attributes that denote "role" (or + # "special-use") of a mailbox. These attributes are included along with base + # attributes defined above. A given mailbox may have none, one, or more than + # one of these attributes. In some cases, a special use is advice to a + # client about what to put in that mailbox. In other cases, it's advice to a + # client about what to expect to find there. + # + # IMAP4 specifies that all mailbox name attributes, including future + # extensions, begin with "\". Net::IMAP returns all mailbox + # attributes as symbols, without the "\" prefix. + # + # The special use attributes were first defined as part of the + # SPECIAL-USE[https://www.rfc-editor.org/rfc/rfc6154.html] extension, but + # servers may return them without including the +SPECIAL-USE+ #capability. + # + # The descriptions here were copied from {[RFC-9051 § + # 7.3.1]}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.3.1]. + # + # Other mailbox name attributes can be found in the {IANA IMAP Mailbox Name + # Attributes registry}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml]. + # ------------------------------------------------------------------------- + + # Mailbox attribute indicating that this mailbox presents all messages in + # the user's message store. Implementations MAY omit some messages, such as, + # perhaps, those in \Trash and \Junk. When this special use is supported, it + # is almost certain to represent a virtual mailbox + ALL = :All + + # Mailbox attribute indicating that this mailbox is used to archive + # messages. The meaning of an "archival" mailbox is server dependent; + # typically, it will be used to get messages out of the inbox, or otherwise + # keep them out of the user's way, while still making them accessible + ARCHIVE = :Archive + + # Mailbox attribute indicating that this mailbox is used to hold draft + # messages -- typically, messages that are being composed but have not yet + # been sent. In some server implementations, this might be a virtual + # mailbox, containing messages from other mailboxes that are marked with the + # "\Draft" message flag. Alternatively, this might just be advice that a + # client put drafts here + DRAFTS = :Drafts + + #-- + # n.b. FLAGGED is defined in the system flags section. + #++ + + # Mailbox attribute indicating that this mailbox is where messages deemed to + # be junk mail are held. Some server implementations might put messages here + # automatically. Alternatively, this might just be advice to a client-side + # spam filter. + JUNK = :Junk + + # Mailbox attribute indicating that this mailbox is used to hold copies of + # messages that have been sent. Some server implementations might put + # messages here automatically. Alternatively, this might just be advice that + # a client save sent messages here. + SENT = :Sent + + # Mailbox attribute indicating that this mailbox is used to hold messages + # that have been deleted or marked for deletion. In some server + # implementations, this might be a virtual mailbox, containing messages from + # other mailboxes that are marked with the +\Deleted+ message flag. + # Alternatively, this might just be advice that a client that chooses not to + # use the \IMAP +\Deleted+ model should use as its trash location. In server + # implementations that strictly expect the \IMAP +\Deleted+ model, this + # special use is likely not to be supported. + TRASH = :Trash + + # :section: + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_data.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_data.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_data.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_data.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,1210 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + autoload :FetchData, "#{__dir__}/fetch_data" + autoload :SearchResult, "#{__dir__}/search_result" + autoload :SequenceSet, "#{__dir__}/sequence_set" + + # Net::IMAP::ContinuationRequest represents command continuation requests. + # + # The command continuation request response is indicated by a "+" token + # instead of a tag. This form of response indicates that the server is + # ready to accept the continuation of a command from the client. The + # remainder of this response is a line of text. + # + class ContinuationRequest < Struct.new(:data, :raw_data) + ## + # method: data + # :call-seq: data -> ResponseText + # + # Returns a ResponseText object + + ## + # method: raw_data + # :call-seq: raw_data -> string + # + # the raw response data + end + + # Net::IMAP::UntaggedResponse represents untagged responses. + # + # Data transmitted by the server to the client and status responses + # that do not indicate command completion are prefixed with the token + # "*", and are called untagged responses. + # + class UntaggedResponse < Struct.new(:name, :data, :raw_data) + ## + # method: name + # :call-seq: name -> string + # + # The uppercase response name, e.g. "FLAGS", "LIST", "FETCH", etc. + + ## + # method: data + # :call-seq: data -> object or nil + # + # The parsed response data, e.g: an array of flag symbols, an array of + # capabilities strings, a ResponseText object, a MailboxList object, a + # FetchData object, a Namespaces object, etc. The response #name + # determines what form the data can take. + + ## + # method: raw_data + # :call-seq: raw_data -> string + # + # The raw response data. + end + + # Net::IMAP::IgnoredResponse represents intentionally ignored responses. + # + # This includes untagged response "NOOP" sent by eg. Zimbra to avoid + # some clients to close the connection. + # + # It matches no IMAP standard. + class IgnoredResponse < UntaggedResponse + end + + # **Note:** This represents an intentionally _unstable_ API. Where + # instances of this class are returned, future releases may return a + # different (incompatible) object without deprecation or warning. + # + # Net::IMAP::UnparsedData represents data for unknown response types or + # unknown extensions to response types without a well-defined extension + # grammar. + # + # See also: UnparsedNumericResponseData, ExtensionData, IgnoredResponse + class UnparsedData < Struct.new(:unparsed_data) + ## + # method: unparsed_data + # :call-seq: unparsed_data -> string + # + # The unparsed data + end + + # **Note:** This represents an intentionally _unstable_ API. Where + # instances of this class are returned, future releases may return a + # different (incompatible) object without deprecation or warning. + # + # Net::IMAP::UnparsedNumericResponseData represents data for unhandled + # response types with a numeric prefix. See the documentation for #number. + # + # See also: UnparsedData, ExtensionData, IgnoredResponse + class UnparsedNumericResponseData < Struct.new(:number, :unparsed_data) + ## + # method: number + # :call-seq: number -> integer + # + # Returns a numeric response data prefix, when available. + # + # Many response types are prefixed with a non-negative #number. For + # message data, #number may represent a sequence number or a UID. For + # mailbox data, #number may represent a message count. + + ## + # method: unparsed_data + # :call-seq: unparsed_data -> string + # + # The unparsed data, not including #number or UntaggedResponse#name. + end + + # **Note:** This represents an intentionally _unstable_ API. Where + # instances of this class are returned, future releases may return a + # different (incompatible) object without deprecation or warning. + # + # Net::IMAP::ExtensionData represents data that is parsable according to the + # forward-compatible extension syntax in RFC3501, RFC4466, or RFC9051, but + # isn't directly known or understood by Net::IMAP yet. + # + # See also: UnparsedData, UnparsedNumericResponseData, IgnoredResponse + class ExtensionData < Struct.new(:data) + ## + # method: data + # :call-seq: data -> string + # + # The parsed extension data. + end + + # Net::IMAP::TaggedResponse represents tagged responses. + # + # The server completion result response indicates the success or + # failure of the operation. It is tagged with the same tag as the + # client command which began the operation. + # + class TaggedResponse < Struct.new(:tag, :name, :data, :raw_data) + ## + # method: tag + # :call-seq: tag -> string + # + # Returns the command tag + + ## + # method: name + # :call-seq: name -> string + # + # Returns the name, one of "OK", "NO", or "BAD". + + ## + # method: data + # :call-seq: data -> ResponseText + # + # Returns a ResponseText object + + ## + # method: raw_data + # :call-seq: raw_data -> string + # + # The raw response data. + end + + # Net::IMAP::ResponseText represents texts of responses. + # + # The text may be prefixed by a ResponseCode. + # + # ResponseText is returned from TaggedResponse#data, or from + # UntaggedResponse#data when the response type is a "condition" ("OK", "NO", + # "BAD", "PREAUTH", or "BYE"). + class ResponseText < Struct.new(:code, :text) + # Used to avoid an allocation when ResponseText is empty + EMPTY = new(nil, "").freeze + + ## + # method: code + # :call-seq: code -> ResponseCode or nil + # + # Returns a ResponseCode, if the response contains one + + ## + # method: text + # :call-seq: text -> string + # + # Returns the response text, not including any response code + end + + # Net::IMAP::ResponseCode represents response codes. Response codes can be + # retrieved from ResponseText#code and can be included in any "condition" + # response: any TaggedResponse and UntaggedResponse when the response type + # is a "condition" ("OK", "NO", "BAD", "PREAUTH", or "BYE"). + # + # Some response codes come with additional data which will be parsed by + # Net::IMAP. Others return +nil+ for #data, but are used as a + # machine-readable annotation for the human-readable ResponseText#text in + # the same response. When Net::IMAP does not know how to parse response + # code text, #data returns the unparsed string. + # + # Untagged response code #data is pushed directly onto Net::IMAP#responses, + # keyed by #name, unless it is removed by the command that generated it. + # Use Net::IMAP#add_response_handler to view tagged response codes for + # command methods that do not return their TaggedResponse. + # + # \IMAP extensions may define new codes and the data that comes with them. + # The IANA {IMAP Response + # Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml] + # registry has links to specifications for all standard response codes. + # Response codes are backwards compatible: Servers are allowed to send new + # response codes even if the client has not enabled the extension that + # defines them. When unknown response code data is encountered, #data + # will return an unparsed string. + # + # ==== +IMAP4rev1+ Response Codes + # See [IMAP4rev1[https://www.rfc-editor.org/rfc/rfc3501]] {§7.1, "Server + # Responses - Status + # Responses"}[https://www.rfc-editor.org/rfc/rfc3501#section-7.1] for full + # definitions of the basic set of IMAP4rev1 response codes: + # * +ALERT+, the ResponseText#text contains a special alert that MUST be + # brought to the user's attention. + # * +BADCHARSET+, #data will be an array of charset strings, or +nil+. + # * +CAPABILITY+, #data will be an array of capability strings. + # * +PARSE+, the ResponseText#text presents an error parsing a message's + # \[RFC5322] or [MIME-IMB] headers. + # * +PERMANENTFLAGS+, followed by an array of flags. System flags will be + # symbols, and keyword flags will be strings. See + # rdoc-ref:Net::IMAP@System+flags + # * +READ-ONLY+, the mailbox was selected read-only, or changed to read-only + # * +READ-WRITE+, the mailbox was selected read-write, or changed to + # read-write + # * +TRYCREATE+, when #append or #copy fail because the target mailbox + # doesn't exist. + # * +UIDNEXT+, #data is an Integer, the next UID value of the mailbox. See + # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]], + # {§2.3.1.1, "Unique Identifier (UID) Message + # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1]. + # * +UIDVALIDITY+, #data is an Integer, the UID validity value of the + # mailbox. See [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501]], + # {§2.3.1.1, "Unique Identifier (UID) Message + # Attribute}[https://www.rfc-editor.org/rfc/rfc3501#section-2.3.1.1]. + # * +UNSEEN+, #data is an Integer, the number of messages which do not have + # the \Seen flag set. + # DEPRECATED by IMAP4rev2. + # + # ==== +BINARY+ extension + # See {[RFC3516]}[https://www.rfc-editor.org/rfc/rfc3516]. + # * +UNKNOWN-CTE+, with a tagged +NO+ response, when the server does not + # known how to decode a CTE (content-transfer-encoding). #data is +nil+. + # See IMAP#fetch. + # + # ==== +UIDPLUS+ extension + # See {[RFC4315 §3]}[https://www.rfc-editor.org/rfc/rfc4315#section-3]. + # * +APPENDUID+, #data is UIDPlusData. See IMAP#append. + # * +COPYUID+, #data is UIDPlusData. See IMAP#copy. + # * +UIDNOTSTICKY+, #data is +nil+. See IMAP#select. + # + # ==== +SEARCHRES+ extension + # See {[RFC5182]}[https://www.rfc-editor.org/rfc/rfc5182]. + # * +NOTSAVED+, with a tagged +NO+ response, when the search result variable + # is not saved. #data is +nil+. + # + # ==== +RFC5530+ Response Codes + # See {[RFC5530]}[https://www.rfc-editor.org/rfc/rfc5530], "IMAP Response + # Codes" for the definition of the following response codes, which are all + # machine-readable annotations for the human-readable ResponseText#text, and + # have +nil+ #data of their own: + # * +UNAVAILABLE+ + # * +AUTHENTICATIONFAILED+ + # * +AUTHORIZATIONFAILED+ + # * +EXPIRED+ + # * +PRIVACYREQUIRED+ + # * +CONTACTADMIN+ + # * +NOPERM+ + # * +INUSE+ + # * +EXPUNGEISSUED+ + # * +CORRUPTION+ + # * +SERVERBUG+ + # * +CLIENTBUG+ + # * +CANNOT+ + # * +LIMIT+ + # * +OVERQUOTA+ + # * +ALREADYEXISTS+ + # * +NONEXISTENT+ + # + # ==== +QRESYNC+ extension + # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. + # * +CLOSED+, returned when the currently selected mailbox is closed + # implicity by selecting or examining another mailbox. #data is +nil+. + # + # ==== +IMAP4rev2+ Response Codes + # See {[RFC9051]}[https://www.rfc-editor.org/rfc/rfc9051] {§7.1, "Server + # Responses - Status + # Responses"}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1] for full + # descriptions of IMAP4rev2 response codes. IMAP4rev2 includes all of the + # response codes listed above (except "UNSEEN") and adds the following: + # * +HASCHILDREN+, with a tagged +NO+ response, when a mailbox delete failed + # because the server doesn't allow deletion of mailboxes with children. + # #data is +nil+. + # + # ==== +CONDSTORE+ extension + # See {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. + # * +NOMODSEQ+, when selecting a mailbox that does not support + # mod-sequences. #data is +nil+. See IMAP#select. + # * +HIGHESTMODSEQ+, #data is an Integer, the highest mod-sequence value of + # all messages in the mailbox. See IMAP#select. + # * +MODIFIED+, #data is a SequenceSet, the messages that have been modified + # since the +UNCHANGEDSINCE+ mod-sequence given to +STORE+ or UID + # STORE. + # + # ==== +OBJECTID+ extension + # See {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. + # * +MAILBOXID+, #data is a string + # + class ResponseCode < Struct.new(:name, :data) + ## + # method: name + # :call-seq: name -> string + # + # Returns the response code name, such as "ALERT", "PERMANENTFLAGS", or + # "UIDVALIDITY". + + ## + # method: data + # :call-seq: data -> object or nil + # + # Returns the parsed response code data, e.g: an array of capabilities + # strings, an array of character set strings, a list of permanent flags, + # an Integer, etc. The response #code determines what form the response + # code data can take. + end + + # Net::IMAP::UIDPlusData represents the ResponseCode#data that accompanies + # the +APPENDUID+ and +COPYUID+ response codes. + # + # See [[UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]]. + # + # ==== Capability requirement + # + # The +UIDPLUS+ capability[rdoc-ref:Net::IMAP#capability] must be supported. + # A server that supports +UIDPLUS+ should send a UIDPlusData object inside + # every TaggedResponse returned by the append[rdoc-ref:Net::IMAP#append], + # copy[rdoc-ref:Net::IMAP#copy], move[rdoc-ref:Net::IMAP#move], {uid + # copy}[rdoc-ref:Net::IMAP#uid_copy], and {uid + # move}[rdoc-ref:Net::IMAP#uid_move] commands---unless the destination + # mailbox reports +UIDNOTSTICKY+. + # + #-- + # TODO: support MULTIAPPEND + #++ + # + class UIDPlusData < Struct.new(:uidvalidity, :source_uids, :assigned_uids) + ## + # method: uidvalidity + # :call-seq: uidvalidity -> nonzero uint32 + # + # The UIDVALIDITY of the destination mailbox. + + ## + # method: source_uids + # :call-seq: source_uids -> nil or an array of nonzero uint32 + # + # The UIDs of the copied or moved messages. + # + # Note:: Returns +nil+ for Net::IMAP#append. + + ## + # method: assigned_uids + # :call-seq: assigned_uids -> an array of nonzero uint32 + # + # The newly assigned UIDs of the copied, moved, or appended messages. + # + # Note:: This always returns an array, even when it contains only one UID. + + ## + # :call-seq: uid_mapping -> nil or a hash + # + # Returns a hash mapping each source UID to the newly assigned destination + # UID. + # + # Note:: Returns +nil+ for Net::IMAP#append. + def uid_mapping + source_uids&.zip(assigned_uids)&.to_h + end + end + + # Net::IMAP::MailboxList represents contents of the LIST response, + # representing a single mailbox path. + # + # Net::IMAP#list returns an array of MailboxList objects. + # + class MailboxList < Struct.new(:attr, :delim, :name) + ## + # method: attr + # :call-seq: attr -> array of Symbols + # + # Returns the name attributes. Each name attribute is a symbol capitalized + # by String#capitalize, such as :Noselect (not :NoSelect). For the + # semantics of each attribute, see: + # * rdoc-ref:Net::IMAP@Basic+Mailbox+Attributes + # * rdoc-ref:Net::IMAP@Mailbox+role+Attributes + # * Net::IMAP@SPECIAL-USE + # * The IANA {IMAP Mailbox Name Attributes + # registry}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml] + + ## + # method: delim + # :call-seq: delim -> single character string + # + # Returns the hierarchy delimiter for the mailbox path. + + ## + # method: name + # :call-seq: name -> string + # + # Returns the mailbox name. + end + + # Net::IMAP::MailboxQuota represents contents of GETQUOTA response. + # This object can also be a response to GETQUOTAROOT. In the syntax + # specification below, the delimiter used with the "#" construct is a + # single space (SPACE). + # + # Net:IMAP#getquota returns an array of MailboxQuota objects. + # + # Net::IMAP#getquotaroot returns an array containing both MailboxQuotaRoot + # and MailboxQuota objects. + # + class MailboxQuota < Struct.new(:mailbox, :usage, :quota) + ## + # method: mailbox + # :call-seq: mailbox -> string + # + # The mailbox with the associated quota. + + ## + # method: usage + # :call-seq: usage -> Integer + # + # Current storage usage of the mailbox. + + ## + # method: quota + # :call-seq: quota -> Integer + # + # Quota limit imposed on the mailbox. + # + end + + # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT + # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.) + # + # Net::IMAP#getquotaroot returns an array containing both MailboxQuotaRoot + # and MailboxQuota objects. + # + class MailboxQuotaRoot < Struct.new(:mailbox, :quotaroots) + ## + # method: mailbox + # :call-seq: mailbox -> string + # + # The mailbox with the associated quota. + + ## + # method: mailbox + # :call-seq: quotaroots -> array of strings + # + # Zero or more quotaroots that affect the quota on the specified mailbox. + end + + # Net::IMAP::MailboxACLItem represents the response from GETACL. + # + # Net::IMAP#getacl returns an array of MailboxACLItem objects. + # + # ==== Required capability + # +ACL+ - described in [ACL[https://tools.ietf.org/html/rfc4314]] + class MailboxACLItem < Struct.new(:user, :rights, :mailbox) + ## + # method: mailbox + # :call-seq: mailbox -> string + # + # The mailbox to which the indicated #user has the specified #rights. + + ## + # method: user + # :call-seq: user -> string + # + # Login name that has certain #rights to the #mailbox that was specified + # with the getacl command. + + ## + # method: rights + # :call-seq: rights -> string + # + # The access rights the indicated #user has to the #mailbox. + end + + # Net::IMAP::Namespace represents a single namespace contained inside a + # NAMESPACE response. + # + # Returned by Net::IMAP#namespace, contained inside a Namespaces object. + # + class Namespace < Struct.new(:prefix, :delim, :extensions) + ## + # method: prefix + # :call-seq: prefix -> string + # + # Returns the namespace prefix string. + + ## + # method: delim + # :call-seq: delim -> single character string or nil + # + # Returns a hierarchy delimiter character, if it exists. + + ## + # method: extensions + # :call-seq: extensions -> Hash[String, Array[String]] + # + # A hash of parameters mapped to arrays of strings, for extensibility. + # Extension parameter semantics would be defined by the extension. + end + + # Net::IMAP::Namespaces represents a +NAMESPACE+ server response, which + # contains lists of #personal, #shared, and #other namespaces. + # + # Net::IMAP#namespace returns a Namespaces object. + # + class Namespaces < Struct.new(:personal, :other, :shared) + ## + # method: personal + # :call-seq: personal -> array of Namespace + # + # Returns an array of Personal Namespace objects. + + ## + # method: other + # :call-seq: other -> array of Namespace + # + # Returns an array of Other Users' Namespace objects. + + ## + # method: shared + # :call-seq: shared -> array of Namespace + # + # Returns an array of Shared Namespace objects. + end + + # Net::IMAP::StatusData represents the contents of the STATUS response. + # + # Net::IMAP#status returns the contents of #attr. + class StatusData < Struct.new(:mailbox, :attr) + ## + # method: mailbox + # :call-seq: mailbox -> string + # + # The mailbox name. + + ## + # method: attr + # :call-seq: attr -> Hash[String, Integer] + # + # A hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT", + # "UIDVALIDITY", "UNSEEN". Each value is a number. + end + + # Net::IMAP::Envelope represents envelope structures of messages. + # + # [Note] + # When the #sender and #reply_to fields are absent or empty, they will + # return the same value as #from. Also, fields may return values that are + # invalid for well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # messages when the message is malformed or a draft message. + # + # See [{IMAP4rev1 §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2]] + # and [{IMAP4rev2 §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2]] + # for full description of the envelope fields, and + # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. + # + # Returned by FetchData#envelope + class Envelope < Struct.new(:date, :subject, :from, :sender, :reply_to, + :to, :cc, :bcc, :in_reply_to, :message_id) + ## + # method: date + # call-seq: date -> string + # + # Returns a string that represents the +Date+ header. + # + # [Note] + # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #date field must not be +nil+. However it can be +nil+ + # for a malformed or draft message. + + ## + # method: subject + # call-seq: subject -> string or nil + # + # Returns a string that represents the +Subject+ header, if it is present. + # + # [Note] + # Servers should return +nil+ when the header is absent and an empty + # string when it is present but empty. Some servers may return a +nil+ + # envelope member in the "present but empty" case. Clients should treat + # +nil+ and empty string as identical. + + ## + # method: from + # call-seq: from -> array of Net::IMAP::Address or nil + # + # Returns an array of Address that represents the +From+ header. + # + # If the +From+ header is absent, or is present but empty, the server + # returns +nil+ for this envelope field. + # + # [Note] + # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #from field must not be +nil+. However it can be +nil+ + # for a malformed or draft message. + + ## + # method: sender + # call-seq: sender -> array of Net::IMAP::Address or nil + # + # Returns an array of Address that represents the +Sender+ header. + # + # [Note] + # If the Sender header is absent, or is present but empty, the + # server sets this field to be the same value as #from. Therefore, in a + # well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] message, + # the #sender envelope field must not be +nil+. However it can be + # +nil+ for a malformed or draft message. + + ## + # method: reply_to + # call-seq: reply_to -> array of Net::IMAP::Address or nil + # + # Returns an array of Address that represents the Reply-To + # header. + # + # [Note] + # If the Reply-To header is absent, or is present but empty, + # the server sets this field to be the same value as #from. Therefore, + # in a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #reply_to envelope field must not be +nil+. However it + # can be +nil+ for a malformed or draft message. + + ## + # method: to + # call-seq: to -> array of Net::IMAP::Address + # + # Returns an array of Address that represents the +To+ header. + + ## + # method: cc + # call-seq: cc -> array of Net::IMAP::Address + # + # Returns an array of Address that represents the +Cc+ header. + + ## + # method: bcc + # call-seq: bcc -> array of Net::IMAP::Address + # + # Returns an array of Address that represents the +Bcc+ header. + + ## + # method: in_reply_to + # call-seq: in_reply_to -> string + # + # Returns a string that represents the In-Reply-To header. + # + # [Note] + # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #in_reply_to field, if present, must not be empty. But + # it can still return an empty string for malformed messages. + # + # Servers should return +nil+ when the header is absent and an empty + # string when it is present but empty. Some servers may return a +nil+ + # envelope member in the "present but empty" case. Clients should treat + # +nil+ and empty string as identical. + + ## + # method: message_id + # call-seq: message_id -> string + # + # Returns a string that represents the Message-ID. + # + # [Note] + # For a well-formed [RFC5322[https://tools.ietf.org/html/rfc5322]] + # message, the #message_id field, if present, must not be empty. But it + # can still return an empty string for malformed messages. + # + # Servers should return +nil+ when the header is absent and an empty + # string when it is present but empty. Some servers may return a +nil+ + # envelope member in the "present but empty" case. Clients should treat + # +nil+ and empty string as identical. + end + + # Net::IMAP::Address represents an electronic mail address, which has been + # parsed into its component parts by the server. Address objects are + # returned within Envelope fields. + # + # === Group syntax + # + # When the #host field is +nil+, this is a special form of address structure + # that indicates the [RFC5322[https://tools.ietf.org/html/rfc5322]] group + # syntax. If the #mailbox name field is also +nil+, this is an end-of-group + # marker (semicolon in RFC-822 syntax). If the #mailbox name field is + # non-+NIL+, this is the start of a group marker, and the mailbox #name + # field holds the group name phrase. + class Address < Struct.new(:name, :route, :mailbox, :host) + ## + # method: name + # :call-seq: name -> string or nil + # + # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] address + # +display-name+ (or the mailbox +phrase+ in the RFC-822 grammar). + + ## + # method: route + # :call-seq: route -> string or nil + # + # Returns the route from RFC-822 route-addr. + # + # Note:: Generating this obsolete route addressing syntax is not allowed + # by [RFC5322[https://tools.ietf.org/html/rfc5322]]. However, + # addresses with this syntax must still be accepted and parsed. + + ## + # method: mailbox + # :call-seq: mailbox -> string or nil + # + # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] address + # +local-part+, if #host is not +nil+. + # + # When #host is +nil+, this returns + # an [RFC5322[https://tools.ietf.org/html/rfc5322]] group name and a +nil+ + # mailbox indicates the end of a group. + + ## + # method: host + # :call-seq: host -> string or nil + # + # Returns the [RFC5322[https://tools.ietf.org/html/rfc5322]] addr-spec + # +domain+ name. + # + # +nil+ indicates [RFC5322[https://tools.ietf.org/html/rfc5322]] group + # syntax. + end + + # Net::IMAP::ContentDisposition represents Content-Disposition fields. + # + class ContentDisposition < Struct.new(:dsp_type, :param) + ## + # method: dsp_type + # :call-seq: dsp_type -> string + # + # Returns the content disposition type, as defined by + # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + + ## + # method: param + # :call-seq: param -> hash + # + # Returns a hash representing parameters of the Content-Disposition + # field, as defined by [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + end + + # Net::IMAP::ThreadMember represents a thread-node returned + # by Net::IMAP#thread. + # + class ThreadMember < Struct.new(:seqno, :children) + ## + # method: seqno + # :call-seq: seqno -> Integer + # + # The message sequence number. + + ## + # method: children + # :call-seq: children -> array of ThreadMember + # + # An array of Net::IMAP::ThreadMember objects for mail items that are + # children of this in the thread. + + # Returns a SequenceSet containing #seqno and all #children's seqno, + # recursively. + def to_sequence_set + SequenceSet.new all_seqnos + end + + protected + + def all_seqnos(node = self) + [node.seqno].concat node.children.flat_map { _1.all_seqnos } + end + + end + + # Net::IMAP::BodyStructure is included by all of the structs that can be + # returned from a "BODYSTRUCTURE" or "BODY" + # FetchData#attr value. Although these classes don't share a base class, + # this module can be used to pattern match all of them. + # + # See {[IMAP4rev1] §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] + # and {[IMAP4rev2] §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2-4.9] + # for full description of all +BODYSTRUCTURE+ fields, and also + # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. + # + # === Classes that include BodyStructure + # BodyTypeBasic:: Represents any message parts that are not handled by + # BodyTypeText, BodyTypeMessage, or BodyTypeMultipart. + # BodyTypeText:: Used by text/* parts. Contains all of the + # BodyTypeBasic fields. + # BodyTypeMessage:: Used by message/rfc822 and + # message/global parts. Contains all of the + # BodyTypeBasic fields. Other message/* types + # should use BodyTypeBasic. + # BodyTypeMultipart:: for multipart/* parts + # + module BodyStructure + end + + # Net::IMAP::BodyTypeBasic represents basic body structures of messages and + # message parts, unless they have a Content-Type that is handled by + # BodyTypeText, BodyTypeMessage, or BodyTypeMultipart. + # + # See {[IMAP4rev1] §7.4.2}[https://www.rfc-editor.org/rfc/rfc3501.html#section-7.4.2] + # and {[IMAP4rev2] §7.5.2}[https://www.rfc-editor.org/rfc/rfc9051.html#section-7.5.2-4.9] + # for full description of all +BODYSTRUCTURE+ fields, and also + # Net::IMAP@Message+envelope+and+body+structure for other relevant RFCs. + # + class BodyTypeBasic < Struct.new(:media_type, :subtype, + :param, :content_id, + :description, :encoding, :size, + :md5, :disposition, :language, + :location, + :extension) + include BodyStructure + + ## + # method: media_type + # :call-seq: media_type -> string + # + # The top-level media type as defined in + # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: subtype + # :call-seq: subtype -> string + # + # The media subtype name as defined in + # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: param + # :call-seq: param -> string + # + # Returns a hash that represents parameters as defined in + # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: content_id + # :call-seq: content_id -> string + # + # Returns a string giving the content id as defined + # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] + # {§7}[https://tools.ietf.org/html/rfc2045#section-7]. + + ## + # method: description + # :call-seq: description -> string + # + # Returns a string giving the content description as defined + # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] + # {§8}[https://tools.ietf.org/html/rfc2045#section-8]. + + ## + # method: encoding + # :call-seq: encoding -> string + # + # Returns a string giving the content transfer encoding as defined + # in [MIME-IMB[https://tools.ietf.org/html/rfc2045]] + # {§6}[https://tools.ietf.org/html/rfc2045#section-6]. + + ## + # method: size + # :call-seq: size -> integer + # + # Returns a number giving the size of the body in octets. + + ## + # method: md5 + # :call-seq: md5 -> string + # + # Returns a string giving the body MD5 value as defined in + # [MD5[https://tools.ietf.org/html/rfc1864]]. + + ## + # method: disposition + # :call-seq: disposition -> ContentDisposition + # + # Returns a ContentDisposition object giving the content + # disposition, as defined by + # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + + ## + # method: language + # :call-seq: language -> string + # + # Returns a string or an array of strings giving the body + # language value as defined in + # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]. + + #-- + ## + # method: location + # :call-seq: location -> string + # + # A string list giving the body content URI as defined in + # [LOCATION[https://www.rfc-editor.org/info/rfc2557]]. + #++ + + ## + # method: extension + # :call-seq: extension -> string + # + # Returns extension data. The +BODYSTRUCTURE+ fetch attribute + # contains extension data, but +BODY+ does not. + + ## + # :call-seq: multipart? -> false + # + # BodyTypeBasic is not used for multipart MIME parts. + def multipart? + return false + end + + # :call-seq: media_subtype -> subtype + # + # >>> + # [Obsolete] + # Use +subtype+ instead. Calling this will generate a warning message + # to +stderr+, then return the value of +subtype+. + #-- + # TODO: why not just keep this as an alias? Would "media_subtype" be used + # for something else? + #++ + def media_subtype + warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) + return subtype + end + end + + # Net::IMAP::BodyTypeText represents the body structures of messages and + # message parts, when Content-Type is text/*. + # + # BodyTypeText contains all of the fields of BodyTypeBasic. See + # BodyTypeBasic for documentation of the following: + # * {media_type}[rdoc-ref:BodyTypeBasic#media_type] + # * subtype[rdoc-ref:BodyTypeBasic#subtype] + # * param[rdoc-ref:BodyTypeBasic#param] + # * {content_id}[rdoc-ref:BodyTypeBasic#content_id] + # * description[rdoc-ref:BodyTypeBasic#description] + # * encoding[rdoc-ref:BodyTypeBasic#encoding] + # * size[rdoc-ref:BodyTypeBasic#size] + # + class BodyTypeText < Struct.new(:media_type, :subtype, + :param, :content_id, + :description, :encoding, :size, + :lines, + :md5, :disposition, :language, + :location, + :extension) + include BodyStructure + + ## + # method: lines + # :call-seq: lines -> Integer + # + # Returns the size of the body in text lines. + + ## + # :call-seq: multipart? -> false + # + # BodyTypeText is not used for multipart MIME parts. + def multipart? + return false + end + + # Obsolete: use +subtype+ instead. Calling this will + # generate a warning message to +stderr+, then return + # the value of +subtype+. + def media_subtype + warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) + return subtype + end + end + + # Net::IMAP::BodyTypeMessage represents the body structures of messages and + # message parts, when Content-Type is message/rfc822 or + # message/global. + # + # BodyTypeMessage contains all of the fields of BodyTypeBasic. See + # BodyTypeBasic for documentation of the following fields: + # * {media_type}[rdoc-ref:BodyTypeBasic#media_type] + # * subtype[rdoc-ref:BodyTypeBasic#subtype] + # * param[rdoc-ref:BodyTypeBasic#param] + # * {content_id}[rdoc-ref:BodyTypeBasic#content_id] + # * description[rdoc-ref:BodyTypeBasic#description] + # * encoding[rdoc-ref:BodyTypeBasic#encoding] + # * size[rdoc-ref:BodyTypeBasic#size] + class BodyTypeMessage < Struct.new(:media_type, :subtype, + :param, :content_id, + :description, :encoding, :size, + :envelope, :body, :lines, + :md5, :disposition, :language, + :location, + :extension) + include BodyStructure + + ## + # method: envelope + # :call-seq: envelope -> Envelope + # + # Returns a Net::IMAP::Envelope giving the envelope structure. + + ## + # method: body + # :call-seq: body -> BodyStructure + # + # Returns a Net::IMAP::BodyStructure for the message's body structure. + + ## + # :call-seq: multipart? -> false + # + # BodyTypeMessage is not used for multipart MIME parts. + def multipart? + return false + end + + # Obsolete: use +subtype+ instead. Calling this will + # generate a warning message to +stderr+, then return + # the value of +subtype+. + def media_subtype + warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) + return subtype + end + end + + # BodyTypeAttachment is not used and will be removed in an upcoming release. + # + # === Bug Analysis + # + # \IMAP body structures are parenthesized lists and assign their fields + # positionally, so missing fields change the intepretation of all + # following fields. Additionally, different body types have a different + # number of required fields, followed by optional "extension" fields. + # + # BodyTypeAttachment was previously returned when a "message/rfc822" part, + # which should be sent as body-type-msg with ten required fields, + # was actually sent as a body-type-basic with _seven_ required + # fields. + # + # basic => type, subtype, param, id, desc, enc, octets, md5=nil, dsp=nil, lang=nil, loc=nil, *ext + # msg => type, subtype, param, id, desc, enc, octets, envelope, body, lines, md5=nil, ... + # + # Normally, +envelope+ and +md5+ are incompatible, but Net::IMAP leniently + # allowed buggy servers to send +NIL+ for +envelope+. As a result, when a + # server sent a message/rfc822 part with +NIL+ for +md5+ and a + # non-NIL +dsp+, Net::IMAP mis-interpreted the + # Content-Disposition as if it were a strange body type. In all + # reported cases, the Content-Disposition was "attachment", so + # BodyTypeAttachment was created as the workaround. + # + # === Current behavior + # + # When interpreted strictly, +envelope+ and +md5+ are incompatible. So the + # current parsing algorithm peeks ahead after it has recieved the seventh + # body field. If the next token is not the start of an +envelope+, we assume + # the server has incorrectly sent us a body-type-basic and return + # BodyTypeBasic. As a result, what was previously BodyTypeMessage#body => + # BodyTypeAttachment is now BodyTypeBasic#disposition => ContentDisposition. + # + class BodyTypeAttachment < Struct.new(:dsp_type, :_unused_, :param) + # *invalid for BodyTypeAttachment* + def media_type + warn(<<~WARN, uplevel: 1) + BodyTypeAttachment#media_type is obsolete. Use dsp_type instead. + WARN + dsp_type + end + + # *invalid for BodyTypeAttachment* + def subtype + warn("BodyTypeAttachment#subtype is obsolete.\n", uplevel: 1) + nil + end + + ## + # method: dsp_type + # :call-seq: dsp_type -> string + # + # Returns the content disposition type, as defined by + # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + + ## + # method: param + # :call-seq: param -> hash + # + # Returns a hash representing parameters of the Content-Disposition + # field, as defined by [DISPOSITION[https://tools.ietf.org/html/rfc2183]]. + + ## + def multipart? + return false + end + end + + deprecate_constant :BodyTypeAttachment + + # Net::IMAP::BodyTypeMultipart represents body structures of messages and + # message parts, when Content-Type is multipart/*. + class BodyTypeMultipart < Struct.new(:media_type, :subtype, + :parts, + :param, :disposition, :language, + :location, + :extension) + include BodyStructure + + ## + # method: media_type + # call-seq: media_type -> "multipart" + # + # BodyTypeMultipart is only used with multipart/* media types. + + ## + # method: subtype + # call-seq: subtype -> string + # + # Returns the content subtype name + # as defined in [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: parts + # call-seq: parts -> array of BodyStructure objects + # + # Returns an array with a BodyStructure object for each part contained in + # this part. + + ## + # method: param + # call-seq: param -> hash + # + # Returns a hash that represents parameters + # as defined in [MIME-IMB[https://tools.ietf.org/html/rfc2045]]. + + ## + # method: disposition + # call-seq: disposition -> ContentDisposition + # + # Returns a Net::IMAP::ContentDisposition object giving the content + # disposition. + + ## + # method: language + # :call-seq: language -> string + # + # Returns a string or an array of strings giving the body + # language value as defined in + # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]. + + ## + # method: extension + # call-seq: extension -> array + # + # Returns extension data as an array of numbers strings, and nested + # arrays (of numbers, strings, etc). + + ## + # :call-seq: multipart? -> true + # + # BodyTypeMultipart is used for multipart MIME parts. + def multipart? + return true + end + + ## + # Obsolete: use +subtype+ instead. Calling this will + # generate a warning message to +stderr+, then return + # the value of +subtype+. + def media_subtype + warn("media_subtype is obsolete, use subtype instead.\n", uplevel: 1) + return subtype + end + end + + # === Obsolete + # BodyTypeExtension is not used and will be removed in an upcoming release. + # + # >>> + # BodyTypeExtension was (incorrectly) used for message/* parts + # (besides message/rfc822, which correctly uses BodyTypeMessage). + # + # Net::IMAP now (correctly) parses all message types (other than + # message/rfc822 or message/global) as BodyTypeBasic. + class BodyTypeExtension < Struct.new(:media_type, :subtype, + :params, :content_id, + :description, :encoding, :size) + def multipart? + return false + end + end + + deprecate_constant :BodyTypeExtension + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_parser/parser_utils.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_parser/parser_utils.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_parser/parser_utils.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_parser/parser_utils.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,240 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + class ResponseParser + # basic utility methods for parsing. + # + # (internal API, subject to change) + module ParserUtils # :nodoc: + + module Generator # :nodoc: + + LOOKAHEAD = "(@token ||= next_token)" + SHIFT_TOKEN = "(@token = nil)" + + # we can skip lexer for single character matches, as a shortcut + def def_char_matchers(name, char, token) + byte = char.ord + match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name + char = char.dump + class_eval <<~RUBY, __FILE__, __LINE__ + 1 + # frozen_string_literal: true + + # force use of #next_token; no string peeking + def lookahead_#{name}? + #{LOOKAHEAD}&.symbol == #{token} + end + + # use token or string peek + def peek_#{name}? + @token ? @token.symbol == #{token} : @str.getbyte(@pos) == #{byte} + end + + # like accept(token_symbols); returns token or nil + def #{name}? + if @token&.symbol == #{token} + #{SHIFT_TOKEN} + #{char} + elsif !@token && @str.getbyte(@pos) == #{byte} + @pos += 1 + #{char} + end + end + + # like match(token_symbols); returns token or raises parse_error + def #{match_name} + if @token&.symbol == #{token} + #{SHIFT_TOKEN} + #{char} + elsif !@token && @str.getbyte(@pos) == #{byte} + @pos += 1 + #{char} + else + parse_error("unexpected %s (expected %p)", + @token&.symbol || @str[@pos].inspect, #{char}) + end + end + RUBY + end + + # TODO: move coersion to the token.value method? + def def_token_matchers(name, *token_symbols, coerce: nil, send: nil) + match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name + + if token_symbols.size == 1 + token = token_symbols.first + matcher = "token&.symbol == %p" % [token] + desc = token + else + matcher = "%p.include? token&.symbol" % [token_symbols] + desc = token_symbols.join(" or ") + end + + value = "(token.value)" + value = coerce.to_s + value if coerce + value = [value, send].join(".") if send + + raise_parse_error = <<~RUBY + parse_error("unexpected %s (expected #{desc})", token&.symbol) + RUBY + + class_eval <<~RUBY, __FILE__, __LINE__ + 1 + # frozen_string_literal: true + + # lookahead version of match, returning the value + def lookahead_#{name}! + token = #{LOOKAHEAD} + if #{matcher} + #{value} + else + #{raise_parse_error} + end + end + + def #{name}? + token = #{LOOKAHEAD} + if #{matcher} + #{SHIFT_TOKEN} + #{value} + end + end + + def #{match_name} + token = #{LOOKAHEAD} + if #{matcher} + #{SHIFT_TOKEN} + #{value} + else + #{raise_parse_error} + end + end + RUBY + end + + end + + private + + # TODO: after checking the lookahead, use a regexp for remaining chars. + # That way a loop isn't needed. + def combine_adjacent(*tokens) + result = "".b + while token = accept(*tokens) + result << token.value + end + if result.empty? + parse_error('unexpected token %s (expected %s)', + lookahead.symbol, tokens.join(" or ")) + end + result + end + + def match(*args) + token = lookahead + unless args.include?(token.symbol) + parse_error('unexpected token %s (expected %s)', + token.symbol.id2name, + args.collect {|i| i.id2name}.join(" or ")) + end + shift_token + token + end + + # like match, but does not raise error on failure. + # + # returns and shifts token on successful match + # returns nil and leaves @token unshifted on no match + def accept(*args) + token = lookahead + if args.include?(token.symbol) + shift_token + token + end + end + + # To be used conditionally: + # assert_no_lookahead if Net::IMAP.debug + def assert_no_lookahead + @token.nil? or + parse_error("assertion failed: expected @token.nil?, actual %s: %p", + @token.symbol, @token.value) + end + + # like accept, without consuming the token + def lookahead?(*symbols) + @token if symbols.include?((@token ||= next_token)&.symbol) + end + + def lookahead + @token ||= next_token + end + + # like match, without consuming the token + def lookahead!(*args) + if args.include?((@token ||= next_token)&.symbol) + @token + else + parse_error('unexpected token %s (expected %s)', + @token&.symbol, args.join(" or ")) + end + end + + def peek_str?(str) + assert_no_lookahead if Net::IMAP.debug + @str[@pos, str.length] == str + end + + def peek_re(re) + assert_no_lookahead if Net::IMAP.debug + re.match(@str, @pos) + end + + def accept_re(re) + assert_no_lookahead if Net::IMAP.debug + re.match(@str, @pos) and @pos = $~.end(0) + $~ + end + + def match_re(re, name) + assert_no_lookahead if Net::IMAP.debug + if re.match(@str, @pos) + @pos = $~.end(0) + $~ + else + parse_error("invalid #{name}") + end + end + + def shift_token + @token = nil + end + + def parse_error(fmt, *args) + msg = format(fmt, *args) + if IMAP.debug + local_path = File.dirname(__dir__) + tok = @token ? "%s: %p" % [@token.symbol, @token.value] : "nil" + warn "%s %s: %s" % [self.class, __method__, msg] + warn " tokenized : %s" % [@str[...@pos].dump] + warn " remaining : %s" % [@str[@pos..].dump] + warn " @lex_state: %s" % [@lex_state] + warn " @pos : %d" % [@pos] + warn " @token : %s" % [tok] + caller_locations(1..20).each_with_index do |cloc, idx| + next unless cloc.path&.start_with?(local_path) + warn " caller[%2d]: %-30s (%s:%d)" % [ + idx, + cloc.base_label, + File.basename(cloc.path, ".rb"), + cloc.lineno + ] + end + end + raise ResponseParseError, msg + end + + end + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_parser.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_parser.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_parser.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/response_parser.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,2120 @@ +# frozen_string_literal: true + +require_relative "errors" +require_relative "response_parser/parser_utils" + +module Net + class IMAP < Protocol + + # Parses an \IMAP server response. + class ResponseParser + include ParserUtils + extend ParserUtils::Generator + + # :call-seq: Net::IMAP::ResponseParser.new -> Net::IMAP::ResponseParser + def initialize + @str = nil + @pos = nil + @lex_state = nil + @token = nil + end + + # :call-seq: + # parse(str) -> ContinuationRequest + # parse(str) -> UntaggedResponse + # parse(str) -> TaggedResponse + # + # Raises ResponseParseError for unparsable strings. + def parse(str) + @str = str + @pos = 0 + @lex_state = EXPR_BEG + @token = nil + return response + end + + private + + # :stopdoc: + + EXPR_BEG = :EXPR_BEG # the default, used in most places + EXPR_DATA = :EXPR_DATA # envelope, body(structure), namespaces + + T_SPACE = :SPACE # atom special + T_ATOM = :ATOM # atom (subset of astring chars) + T_NIL = :NIL # subset of atom and label + T_NUMBER = :NUMBER # subset of atom + T_LBRA = :LBRA # subset of atom + T_PLUS = :PLUS # subset of atom; tag special + T_RBRA = :RBRA # atom special; resp_special; valid astring char + T_QUOTED = :QUOTED # starts/end with atom special + T_BSLASH = :BSLASH # atom special; quoted special + T_LPAR = :LPAR # atom special; paren list delimiter + T_RPAR = :RPAR # atom special; paren list delimiter + T_STAR = :STAR # atom special; list wildcard + T_PERCENT = :PERCENT # atom special; list wildcard + T_LITERAL = :LITERAL # starts with atom special + T_LITERAL8 = :LITERAL8 # starts with atom char "~" + T_CRLF = :CRLF # atom special; text special; quoted special + T_TEXT = :TEXT # any char except CRLF + T_EOF = :EOF # end of response string + + module ResponseConditions + OK = "OK" + NO = "NO" + BAD = "BAD" + BYE = "BYE" + PREAUTH = "PREAUTH" + + RESP_COND_STATES = [OK, NO, BAD ].freeze + RESP_DATA_CONDS = [OK, NO, BAD, BYE, ].freeze + AUTH_CONDS = [OK, PREAUTH].freeze + GREETING_CONDS = [OK, BYE, PREAUTH].freeze + RESP_CONDS = [OK, NO, BAD, BYE, PREAUTH].freeze + end + include ResponseConditions + + module Patterns + + module CharClassSubtraction + refine Regexp do + def -(rhs); /[#{source}&&[^#{rhs.source}]]/n.freeze end + end + end + using CharClassSubtraction + + # From RFC5234, "Augmented BNF for Syntax Specifications: ABNF" + # >>> + # ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + # CHAR = %x01-7F + # CRLF = CR LF + # ; Internet standard newline + # CTL = %x00-1F / %x7F + # ; controls + # DIGIT = %x30-39 + # ; 0-9 + # DQUOTE = %x22 + # ; " (Double Quote) + # HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + # OCTET = %x00-FF + # SP = %x20 + module RFC5234 + ALPHA = /[A-Za-z]/n + CHAR = /[\x01-\x7f]/n + CRLF = /\r\n/n + CTL = /[\x00-\x1F\x7F]/n + DIGIT = /\d/n + DQUOTE = /"/n + HEXDIG = /\h/ + OCTET = /[\x00-\xFF]/n # not using /./m for embedding purposes + SP = / /n + end + + # UTF-8, a transformation format of ISO 10646 + # >>> + # UTF8-1 = %x00-7F + # UTF8-tail = %x80-BF + # UTF8-2 = %xC2-DF UTF8-tail + # UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / + # %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) + # UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / + # %xF4 %x80-8F 2( UTF8-tail ) + # UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 + # UTF8-octets = *( UTF8-char ) + # + # n.b. String * Integer is used for repetition, rather than /x{3}/, + # because ruby 3.2's linear-time cache-based optimization doesn't work + # with "bounded or fixed times repetition nesting in another repetition + # (e.g. /(a{2,3})*/). It is an implementation issue entirely, but we + # believe it is hard to support this case correctly." + # See https://bugs.ruby-lang.org/issues/19104 + module RFC3629 + UTF8_1 = /[\x00-\x7f]/n # aka ASCII 7bit + UTF8_TAIL = /[\x80-\xBF]/n + UTF8_2 = /[\xC2-\xDF]#{UTF8_TAIL}/n + UTF8_3 = Regexp.union(/\xE0[\xA0-\xBF]#{UTF8_TAIL}/n, + /\xED[\x80-\x9F]#{UTF8_TAIL}/n, + /[\xE1-\xEC]#{ UTF8_TAIL.source * 2}/n, + /[\xEE-\xEF]#{ UTF8_TAIL.source * 2}/n) + UTF8_4 = Regexp.union(/[\xF1-\xF3]#{ UTF8_TAIL.source * 3}/n, + /\xF0[\x90-\xBF]#{UTF8_TAIL.source * 2}/n, + /\xF4[\x80-\x8F]#{UTF8_TAIL.source * 2}/n) + UTF8_CHAR = Regexp.union(UTF8_1, UTF8_2, UTF8_3, UTF8_4) + UTF8_OCTETS = /#{UTF8_CHAR}*/n + end + + include RFC5234 + include RFC3629 + + # CHAR8 = %x01-ff + # ; any OCTET except NUL, %x00 + CHAR8 = /[\x01-\xff]/n + + # list-wildcards = "%" / "*" + LIST_WILDCARDS = /[%*]/n + # quoted-specials = DQUOTE / "\" + QUOTED_SPECIALS = /["\\]/n + # resp-specials = "]" + RESP_SPECIALS = /[\]]/n + + # atomish = 1* + # ; We use "atomish" for msg-att and section, in order + # ; to simplify "BODY[HEADER.FIELDS (foo bar)]". + # + # atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards / + # quoted-specials / resp-specials + # ATOM-CHAR = + # atom = 1*ATOM-CHAR + # ASTRING-CHAR = ATOM-CHAR / resp-specials + # tag = 1* + + ATOM_SPECIALS = /[(){ \x00-\x1f\x7f%*"\\\]]/n + ASTRING_SPECIALS = /[(){ \x00-\x1f\x7f%*"\\]/n + + ASTRING_CHAR = CHAR - ASTRING_SPECIALS + ATOM_CHAR = CHAR - ATOM_SPECIALS + + ATOM = /#{ATOM_CHAR}+/n + ASTRING_CHARS = /#{ASTRING_CHAR}+/n + ATOMISH = /#{ATOM_CHAR - /[\[]/ }+/ + TAG = /#{ASTRING_CHAR - /[+]/ }+/ + + # TEXT-CHAR = + TEXT_CHAR = CHAR - /[\r\n]/ + + # resp-text-code = ... / atom [SP 1*] + CODE_TEXT_CHAR = TEXT_CHAR - RESP_SPECIALS + CODE_TEXT = /#{CODE_TEXT_CHAR}+/n + + # flag = "\Answered" / "\Flagged" / "\Deleted" / + # "\Seen" / "\Draft" / flag-keyword / flag-extension + # ; Does not include "\Recent" + # flag-extension = "\" atom + # ; Future expansion. Client implementations + # ; MUST accept flag-extension flags. Server + # ; implementations MUST NOT generate + # ; flag-extension flags except as defined by + # ; a future Standard or Standards Track + # ; revisions of this specification. + # flag-keyword = "$MDNSent" / "$Forwarded" / "$Junk" / + # "$NotJunk" / "$Phishing" / atom + # + # flag-perm = flag / "\*" + # + # Not checking for max one mbx-list-sflag in the parser. + # >>> + # mbx-list-oflag = "\Noinferiors" / child-mbox-flag / + # "\Subscribed" / "\Remote" / flag-extension + # ; Other flags; multiple from this list are + # ; possible per LIST response, but each flag + # ; can only appear once per LIST response + # mbx-list-sflag = "\NonExistent" / "\Noselect" / "\Marked" / + # "\Unmarked" + # ; Selectability flags; only one per LIST response + # child-mbox-flag = "\HasChildren" / "\HasNoChildren" + # ; attributes for the CHILDREN return option, at most + # ; one possible per LIST response + FLAG = /\\?#{ATOM}/n + FLAG_EXTENSION = /\\#{ATOM}/n + FLAG_KEYWORD = ATOM + FLAG_PERM = Regexp.union(FLAG, "\\*") + MBX_FLAG = FLAG_EXTENSION + + # flag-list = "(" [flag *(SP flag)] ")" + # resp-text-code =/ "PERMANENTFLAGS" SP + # "(" [flag-perm *(SP flag-perm)] ")" + # mbx-list-flags = *(mbx-list-oflag SP) mbx-list-sflag + # *(SP mbx-list-oflag) / + # mbx-list-oflag *(SP mbx-list-oflag) + # (Not checking for max one mbx-list-sflag in the parser.) + FLAG_LIST = /\G\((#{FLAG }(?:#{SP}#{FLAG })*|)\)/ni + FLAG_PERM_LIST = /\G\((#{FLAG_PERM}(?:#{SP}#{FLAG_PERM})*|)\)/ni + MBX_LIST_FLAGS = /\G (#{MBX_FLAG }(?:#{SP}#{MBX_FLAG })*) /nix + + # Gmail allows SP and "]" in flags....... + QUIRKY_FLAG = Regexp.union(/\\?#{ASTRING_CHARS}/n, "\\*") + QUIRKY_FLAGS_LIST = /\G\(( [^)]* )\)/nx + + # RFC3501: + # QUOTED-CHAR = / + # "\" quoted-specials + # RFC9051: + # QUOTED-CHAR = / + # "\" quoted-specials / UTF8-2 / UTF8-3 / UTF8-4 + # RFC3501 & RFC9051: + # quoted = DQUOTE *QUOTED-CHAR DQUOTE + QUOTED_CHAR_safe = TEXT_CHAR - QUOTED_SPECIALS + QUOTED_CHAR_esc = /\\#{QUOTED_SPECIALS}/n + QUOTED_CHAR_rev1 = Regexp.union(QUOTED_CHAR_safe, QUOTED_CHAR_esc) + QUOTED_CHAR_rev2 = Regexp.union(QUOTED_CHAR_rev1, + UTF8_2, UTF8_3, UTF8_4) + QUOTED_rev1 = /"(#{QUOTED_CHAR_rev1}*)"/n + QUOTED_rev2 = /"(#{QUOTED_CHAR_rev2}*)"/n + + # RFC3501: + # text = 1*TEXT-CHAR + # RFC9051: + # text = 1*(TEXT-CHAR / UTF8-2 / UTF8-3 / UTF8-4) + # ; Non-ASCII text can only be returned + # ; after ENABLE IMAP4rev2 command + TEXT_rev1 = /#{TEXT_CHAR}+/ + TEXT_rev2 = /#{Regexp.union TEXT_CHAR, UTF8_2, UTF8_3, UTF8_4}+/ + + # tagged-label-fchar = ALPHA / "-" / "_" / "." + TAGGED_LABEL_FCHAR = /[a-zA-Z\-_.]/n + # tagged-label-char = tagged-label-fchar / DIGIT / ":" + TAGGED_LABEL_CHAR = /[a-zA-Z\-_.0-9:]*/n + # tagged-ext-label = tagged-label-fchar *tagged-label-char + # ; Is a valid RFC 3501 "atom". + TAGGED_EXT_LABEL = /#{TAGGED_LABEL_FCHAR}#{TAGGED_LABEL_CHAR}*/n + + # nz-number = digit-nz *DIGIT + # ; Non-zero unsigned 32-bit integer + # ; (0 < n < 4,294,967,296) + NZ_NUMBER = /[1-9]\d*/n + + # seq-number = nz-number / "*" + # ; message sequence number (COPY, FETCH, STORE + # ; commands) or unique identifier (UID COPY, + # ; UID FETCH, UID STORE commands). + # ; * represents the largest number in use. In + # ; the case of message sequence numbers, it is + # ; the number of messages in a non-empty mailbox. + # ; In the case of unique identifiers, it is the + # ; unique identifier of the last message in the + # ; mailbox or, if the mailbox is empty, the + # ; mailbox's current UIDNEXT value. + # ; The server should respond with a tagged BAD + # ; response to a command that uses a message + # ; sequence number greater than the number of + # ; messages in the selected mailbox. This + # ; includes "*" if the selected mailbox is empty. + SEQ_NUMBER = /#{NZ_NUMBER}|\*/n + + # seq-range = seq-number ":" seq-number + # ; two seq-number values and all values between + # ; these two regardless of order. + # ; Example: 2:4 and 4:2 are equivalent and + # ; indicate values 2, 3, and 4. + # ; Example: a unique identifier sequence range of + # ; 3291:* includes the UID of the last message in + # ; the mailbox, even if that value is less than + # ; 3291. + SEQ_RANGE = /#{SEQ_NUMBER}:#{SEQ_NUMBER}/n + + # sequence-set = (seq-number / seq-range) ["," sequence-set] + # ; set of seq-number values, regardless of order. + # ; Servers MAY coalesce overlaps and/or execute + # ; the sequence in any order. + # ; Example: a message sequence number set of + # ; 2,4:7,9,12:* for a mailbox with 15 messages is + # ; equivalent to 2,4,5,6,7,9,12,13,14,15 + # ; Example: a message sequence number set of + # ; *:4,5:7 for a mailbox with 10 messages is + # ; equivalent to 10,9,8,7,6,5,4,5,6,7 and MAY + # ; be reordered and overlap coalesced to be + # ; 4,5,6,7,8,9,10. + SEQUENCE_SET_ITEM = /#{SEQ_NUMBER}|#{SEQ_RANGE}/n + SEQUENCE_SET = /#{SEQUENCE_SET_ITEM}(?:,#{SEQUENCE_SET_ITEM})*/n + SEQUENCE_SET_STR = /\A#{SEQUENCE_SET}\z/n + + # RFC3501: + # literal = "{" number "}" CRLF *CHAR8 + # ; Number represents the number of CHAR8s + # RFC9051: + # literal = "{" number64 ["+"] "}" CRLF *CHAR8 + # ; represents the number of CHAR8s. + # ; A non-synchronizing literal is distinguished + # ; from a synchronizing literal by the presence of + # ; "+" before the closing "}". + # ; Non-synchronizing literals are not allowed when + # ; sent from server to the client. + LITERAL = /\{(\d+)\}\r\n/n + + # RFC3516 (BINARY): + # literal8 = "~{" number "}" CRLF *OCTET + # ; represents the number of OCTETs + # ; in the response string. + # RFC9051: + # literal8 = "~{" number64 "}" CRLF *OCTET + # ; represents the number of OCTETs + # ; in the response string. + LITERAL8 = /~\{(\d+)\}\r\n/n + + module_function + + def unescape_quoted!(quoted) + quoted + &.gsub!(/\\(#{QUOTED_SPECIALS})/n, "\\1") + &.force_encoding("UTF-8") + end + + def unescape_quoted(quoted) + quoted + &.gsub(/\\(#{QUOTED_SPECIALS})/n, "\\1") + &.force_encoding("UTF-8") + end + + end + + # the default, used in most places + BEG_REGEXP = /\G(?:\ +(?# 1: SPACE )( )|\ +(?# 2: LITERAL8)#{Patterns::LITERAL8}|\ +(?# 3: ATOM prefixed with a compatible subtype)\ +((?:\ +(?# 4: NIL )(NIL)|\ +(?# 5: NUMBER )(\d+)|\ +(?# 6: PLUS )(\+))\ +(?# 7: ATOM remaining after prefix )(#{Patterns::ATOMISH})?\ +(?# This enables greedy alternation without lookahead, in linear time.)\ +)|\ +(?# Also need to check for ATOM without a subtype prefix.)\ +(?# 8: ATOM )(#{Patterns::ATOMISH})|\ +(?# 9: QUOTED )#{Patterns::QUOTED_rev2}|\ +(?# 10: LPAR )(\()|\ +(?# 11: RPAR )(\))|\ +(?# 12: BSLASH )(\\)|\ +(?# 13: STAR )(\*)|\ +(?# 14: LBRA )(\[)|\ +(?# 15: RBRA )(\])|\ +(?# 16: LITERAL )#{Patterns::LITERAL}|\ +(?# 17: PERCENT )(%)|\ +(?# 18: CRLF )(\r\n)|\ +(?# 19: EOF )(\z))/ni + + # envelope, body(structure), namespaces + DATA_REGEXP = /\G(?:\ +(?# 1: SPACE )( )|\ +(?# 2: NIL )(NIL)|\ +(?# 3: NUMBER )(\d+)|\ +(?# 4: QUOTED )#{Patterns::QUOTED_rev2}|\ +(?# 5: LITERAL )#{Patterns::LITERAL}|\ +(?# 6: LPAR )(\()|\ +(?# 7: RPAR )(\)))/ni + + # text, after 'resp-text-code "]"' + TEXT_REGEXP = /\G(#{Patterns::TEXT_rev2})/n + + # resp-text-code, after 'atom SP' + CTEXT_REGEXP = /\G(#{Patterns::CODE_TEXT})/n + + Token = Struct.new(:symbol, :value) + + def_char_matchers :SP, " ", :T_SPACE + def_char_matchers :PLUS, "+", :T_PLUS + def_char_matchers :STAR, "*", :T_STAR + + def_char_matchers :lpar, "(", :T_LPAR + def_char_matchers :rpar, ")", :T_RPAR + + def_char_matchers :lbra, "[", :T_LBRA + def_char_matchers :rbra, "]", :T_RBRA + + # valid number ranges are not enforced by parser + # number = 1*DIGIT + # ; Unsigned 32-bit integer + # ; (0 <= n < 4,294,967,296) + def_token_matchers :number, T_NUMBER, coerce: Integer + + def_token_matchers :quoted, T_QUOTED + + # string = quoted / literal + def_token_matchers :string, T_QUOTED, T_LITERAL + + # used by nstring8 = nstring / literal8 + def_token_matchers :string8, T_QUOTED, T_LITERAL, T_LITERAL8 + + # use where string represents "LABEL" values + def_token_matchers :case_insensitive__string, + T_QUOTED, T_LITERAL, + send: :upcase + + # n.b: NIL? and NIL! return the "NIL" atom string (truthy) on success. + # NIL? returns nil when it does *not* match + def_token_matchers :NIL, T_NIL + + # In addition to explicitly uses of +tagged-ext-label+, use this to match + # keywords when the grammar has not provided any extension syntax. + # + # Do *not* use this for labels where the grammar specifies extensions + # can be +atom+, even if all currently defined labels would match. For + # example response codes in +resp-text-code+. + # + # tagged-ext-label = tagged-label-fchar *tagged-label-char + # ; Is a valid RFC 3501 "atom". + # tagged-label-fchar = ALPHA / "-" / "_" / "." + # tagged-label-char = tagged-label-fchar / DIGIT / ":" + # + # TODO: add to lexer and only match tagged-ext-label + def_token_matchers :tagged_ext_label, T_ATOM, T_NIL, send: :upcase + + def_token_matchers :CRLF, T_CRLF + def_token_matchers :EOF, T_EOF + + # atom = 1*ATOM-CHAR + # ATOM-CHAR = + ATOM_TOKENS = [T_ATOM, T_NUMBER, T_NIL, T_LBRA, T_PLUS] + + SEQUENCE_SET_TOKENS = [T_ATOM, T_NUMBER, T_STAR] + + # sequence-set = (seq-number / seq-range) ["," sequence-set] + # sequence-set =/ seq-last-command + # ; Allow for "result of the last command" + # ; indicator. + # seq-last-command = "$" + # + # *note*: doesn't match seq-last-command + def sequence_set + str = combine_adjacent(*SEQUENCE_SET_TOKENS) + if Patterns::SEQUENCE_SET_STR.match?(str) + SequenceSet[str] + else + parse_error("unexpected atom %p, expected sequence-set", str) + end + end + + # ASTRING-CHAR = ATOM-CHAR / resp-specials + # resp-specials = "]" + ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA].freeze + + ASTRING_TOKENS = [T_QUOTED, *ASTRING_CHARS_TOKENS, T_LITERAL].freeze + + # tag = 1* + TAG_TOKENS = (ASTRING_CHARS_TOKENS - [T_PLUS]).freeze + + # TODO: handle atom, astring_chars, and tag entirely inside the lexer + def atom; combine_adjacent(*ATOM_TOKENS) end + def astring_chars; combine_adjacent(*ASTRING_CHARS_TOKENS) end + def tag; combine_adjacent(*TAG_TOKENS) end + + # the #accept version of #atom + def atom?; -combine_adjacent(*ATOM_TOKENS) if lookahead?(*ATOM_TOKENS) end + + # Returns atom.upcase + def case_insensitive__atom; -combine_adjacent(*ATOM_TOKENS).upcase end + + # Returns atom?&.upcase + def case_insensitive__atom? + -combine_adjacent(*ATOM_TOKENS).upcase if lookahead?(*ATOM_TOKENS) + end + + # astring = 1*ASTRING-CHAR / string + def astring + lookahead?(*ASTRING_CHARS_TOKENS) ? astring_chars : string + end + + def astring? + lookahead?(*ASTRING_CHARS_TOKENS) ? astring_chars : string? + end + + # Use #label or #label_in to assert specific known labels + # (+tagged-ext-label+ only, not +atom+). + def label(word) + (val = tagged_ext_label) == word and return val + parse_error("unexpected atom %p, expected %p instead", val, word) + end + + # Use #label or #label_in to assert specific known labels + # (+tagged-ext-label+ only, not +atom+). + def label_in(*labels) + lbl = tagged_ext_label and labels.include?(lbl) and return lbl + parse_error("unexpected atom %p, expected one of %s instead", + lbl, labels.join(" or ")) + end + + # expects "OK" or "PREAUTH" and raises InvalidResponseError on failure + def resp_cond_auth__name + lbl = tagged_ext_label and AUTH_CONDS.include? lbl and return lbl + raise InvalidResponseError, "bad response type %p, expected %s" % [ + lbl, AUTH_CONDS.join(" or ") + ] + end + + # expects "OK" or "NO" or "BAD" and raises InvalidResponseError on failure + def resp_cond_state__name + lbl = tagged_ext_label and RESP_COND_STATES.include? lbl and return lbl + raise InvalidResponseError, "bad response type %p, expected %s" % [ + lbl, RESP_COND_STATES.join(" or ") + ] + end + + # nstring = string / nil + def nstring + NIL? ? nil : string + end + + def nstring8 + NIL? ? nil : string8 + end + + def nquoted + NIL? ? nil : quoted + end + + # use where nstring represents "LABEL" values + def case_insensitive__nstring + NIL? ? nil : case_insensitive__string + end + + # tagged-ext-comp = astring / + # tagged-ext-comp *(SP tagged-ext-comp) / + # "(" tagged-ext-comp ")" + # ; Extensions that follow this general + # ; syntax should use nstring instead of + # ; astring when appropriate in the context + # ; of the extension. + # ; Note that a message set or a "number" + # ; can always be represented as an "atom". + # ; A URL should be represented as + # ; a "quoted" string. + def tagged_ext_comp + vals = [] + while true + vals << case lookahead!(*ASTRING_TOKENS, T_LPAR).symbol + when T_LPAR then lpar; ary = tagged_ext_comp; rpar; ary + when T_NUMBER then number + else astring + end + SP? or break + end + vals + end + + # tagged-ext-simple is a subset of atom + # TODO: recognize sequence-set in the lexer + # + # tagged-ext-simple = sequence-set / number / number64 + def tagged_ext_simple + number? || sequence_set + end + + # tagged-ext-val = tagged-ext-simple / + # "(" [tagged-ext-comp] ")" + def tagged_ext_val + if lpar? + _ = peek_rpar? ? [] : tagged_ext_comp + rpar + _ + else + tagged_ext_simple + end + end + + # mailbox = "INBOX" / astring + # ; INBOX is case-insensitive. All case variants of + # ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX + # ; not as an astring. An astring which consists of + # ; the case-insensitive sequence "I" "N" "B" "O" "X" + # ; is considered to be INBOX and not an astring. + # ; Refer to section 5.1 for further + # ; semantic details of mailbox names. + alias mailbox astring + + # valid number ranges are not enforced by parser + # number64 = 1*DIGIT + # ; Unsigned 63-bit integer + # ; (0 <= n <= 9,223,372,036,854,775,807) + alias number64 number + alias number64? number? + + # valid number ranges are not enforced by parser + # nz-number = digit-nz *DIGIT + # ; Non-zero unsigned 32-bit integer + # ; (0 < n < 4,294,967,296) + alias nz_number number + alias nz_number? number? + + # valid number ranges are not enforced by parser + # nz-number64 = digit-nz *DIGIT + # ; Unsigned 63-bit integer + # ; (0 < n <= 9,223,372,036,854,775,807) + alias nz_number64 nz_number + + # valid number ranges are not enforced by parser + # uniqueid = nz-number + # ; Strictly ascending + alias uniqueid nz_number + + # valid number ranges are not enforced by parser + # + # a 64-bit unsigned integer and is the decimal equivalent for the ID hex + # string used in the web interface and the Gmail API. + alias x_gm_id number + + # [RFC3501 & RFC9051:] + # response = *(continue-req / response-data) response-done + # + # For simplicity, response isn't interpreted as the combination of the + # three response types, but instead represents any individual server + # response. Our simplified interpretation is defined as: + # response = continue-req | response_data | response-tagged + # + # n.b: our "response-tagged" definition parses "greeting" too. + def response + resp = case lookahead!(T_PLUS, T_STAR, *TAG_TOKENS).symbol + when T_PLUS then continue_req + when T_STAR then response_data + else response_tagged + end + accept_spaces # QUIRKY: Ignore trailing space (MS Exchange Server?) + CRLF! + EOF! + resp + end + + # RFC3501 & RFC9051: + # continue-req = "+" SP (resp-text / base64) CRLF + # + # n.b: base64 is valid resp-text. And in the spirit of RFC9051 Appx E 23 + # (and to workaround existing servers), we use the following grammar: + # + # continue-req = "+" (SP (resp-text)) CRLF + def continue_req + PLUS! + ContinuationRequest.new(SP? ? resp_text : ResponseText::EMPTY, @str) + end + + RE_RESPONSE_TYPE = /\G(?:\d+ )?(?#{Patterns::TAGGED_EXT_LABEL})/n + + # [RFC3501:] + # response-data = "*" SP (resp-cond-state / resp-cond-bye / + # mailbox-data / message-data / capability-data) CRLF + # [RFC4466:] + # response-data = "*" SP response-payload CRLF + # response-payload = resp-cond-state / resp-cond-bye / + # mailbox-data / message-data / capability-data + # RFC5161 (ENABLE capability): + # response-data =/ "*" SP enable-data CRLF + # RFC5255 (LANGUAGE capability) + # response-payload =/ language-data + # RFC5255 (I18NLEVEL=1 and I18NLEVEL=2 capabilities) + # response-payload =/ comparator-data + # [RFC9051:] + # response-data = "*" SP (resp-cond-state / resp-cond-bye / + # mailbox-data / message-data / capability-data / + # enable-data) CRLF + # + # [merging in greeting and response-fatal:] + # greeting = "*" SP (resp-cond-auth / resp-cond-bye) CRLF + # response-fatal = "*" SP resp-cond-bye CRLF + # response-data =/ "*" SP (resp-cond-auth / resp-cond-bye) CRLF + # [removing duplicates, this is simply] + # response-payload =/ resp-cond-auth + # + # TODO: remove resp-cond-auth and handle greeting separately + def response_data + STAR!; SP! + m = peek_re(RE_RESPONSE_TYPE) or parse_error("unparsable response") + case m["type"].upcase + when "OK" then resp_cond_state__untagged # RFC3501, RFC9051 + when "FETCH" then message_data__fetch # RFC3501, RFC9051 + when "EXPUNGE" then message_data__expunge # RFC3501, RFC9051 + when "EXISTS" then mailbox_data__exists # RFC3501, RFC9051 + when "ESEARCH" then esearch_response # RFC4731, RFC9051, etc + when "VANISHED" then expunged_resp # RFC7162 + when "UIDFETCH" then uidfetch_resp # (draft) UIDONLY + when "SEARCH" then mailbox_data__search # RFC3501 (obsolete) + when "CAPABILITY" then capability_data__untagged # RFC3501, RFC9051 + when "FLAGS" then mailbox_data__flags # RFC3501, RFC9051 + when "LIST" then mailbox_data__list # RFC3501, RFC9051 + when "STATUS" then mailbox_data__status # RFC3501, RFC9051 + when "NAMESPACE" then namespace_response # RFC2342, RFC9051 + when "ENABLED" then enable_data # RFC5161, RFC9051 + when "BAD" then resp_cond_state__untagged # RFC3501, RFC9051 + when "NO" then resp_cond_state__untagged # RFC3501, RFC9051 + when "PREAUTH" then resp_cond_auth # RFC3501, RFC9051 + when "BYE" then resp_cond_bye # RFC3501, RFC9051 + when "RECENT" then mailbox_data__recent # RFC3501 (obsolete) + when "SORT" then sort_data # RFC5256, RFC7162 + when "THREAD" then thread_data # RFC5256 + when "QUOTA" then quota_response # RFC2087, RFC9208 + when "QUOTAROOT" then quotaroot_response # RFC2087, RFC9208 + when "ID" then id_response # RFC2971 + when "ACL" then acl_data # RFC4314 + when "LISTRIGHTS" then listrights_data # RFC4314 + when "MYRIGHTS" then myrights_data # RFC4314 + when "METADATA" then metadata_resp # RFC5464 + when "LANGUAGE" then language_data # RFC5255 + when "COMPARATOR" then comparator_data # RFC5255 + when "CONVERTED" then message_data__converted # RFC5259 + when "LSUB" then mailbox_data__lsub # RFC3501 (obsolete) + when "XLIST" then mailbox_data__xlist # deprecated + when "NOOP" then response_data__noop + else response_data__unhandled + end + end + + def response_data__unhandled(klass = UntaggedResponse) + num = number?; SP? + type = tagged_ext_label; SP? + text = remaining_unparsed + data = + if num && text then UnparsedNumericResponseData.new(num, text) + elsif text then UnparsedData.new(text) + else num + end + klass.new(type, data, @str) + end + + # reads all the way up until CRLF + def remaining_unparsed + str = @str[@pos...-2] and @pos += str.bytesize + str&.empty? ? nil : str + end + + def response_data__ignored; response_data__unhandled(IgnoredResponse) end + alias response_data__noop response_data__ignored + + alias esearch_response response_data__unhandled + alias expunged_resp response_data__unhandled + alias uidfetch_resp response_data__unhandled + alias listrights_data response_data__unhandled + alias myrights_data response_data__unhandled + alias metadata_resp response_data__unhandled + alias language_data response_data__unhandled + alias comparator_data response_data__unhandled + alias message_data__converted response_data__unhandled + + # RFC3501 & RFC9051: + # response-tagged = tag SP resp-cond-state CRLF + def response_tagged + TaggedResponse.new(tag, *(SP!; resp_cond_state), @str) + end + + # RFC3501 & RFC9051: + # resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text + # + # NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing + # servers), we don't require a final SP and instead parse this as: + # + # resp-cond-state = ("OK" / "NO" / "BAD") [SP resp-text] + def resp_cond_state + [resp_cond_state__name, SP? ? resp_text : ResponseText::EMPTY] + end + + def resp_cond_state__untagged + UntaggedResponse.new(*resp_cond_state, @str) + end + + # resp-cond-auth = ("OK" / "PREAUTH") SP resp-text + # + # NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing + # servers), we don't require a final SP and instead parse this as: + # + # resp-cond-auth = ("OK" / "PREAUTH") [SP resp-text] + def resp_cond_auth + UntaggedResponse.new(resp_cond_auth__name, + SP? ? resp_text : ResponseText::EMPTY, + @str) + end + + # resp-cond-bye = "BYE" SP resp-text + # + # NOTE: In the spirit of RFC9051 Appx E 23 (and to workaround existing + # servers), we don't require a final SP and instead parse this as: + # + # resp-cond-bye = "BYE" [SP resp-text] + def resp_cond_bye + UntaggedResponse.new(label(BYE), + SP? ? resp_text : ResponseText::EMPTY, + @str) + end + + # message-data = nz-number SP ("EXPUNGE" / ("FETCH" SP msg-att)) + def message_data__fetch + seq = nz_number; SP! + name = label "FETCH"; SP! + data = FetchData.new(seq, msg_att(seq)) + UntaggedResponse.new(name, data, @str) + end + + def response_data__simple_numeric + data = nz_number; SP! + name = tagged_ext_label + UntaggedResponse.new(name, data, @str) + end + + alias message_data__expunge response_data__simple_numeric + alias mailbox_data__exists response_data__simple_numeric + alias mailbox_data__recent response_data__simple_numeric + + # RFC3501 & RFC9051: + # msg-att = "(" (msg-att-dynamic / msg-att-static) + # *(SP (msg-att-dynamic / msg-att-static)) ")" + # + # msg-att-dynamic = "FLAGS" SP "(" [flag-fetch *(SP flag-fetch)] ")" + # RFC5257 (ANNOTATE extension): + # msg-att-dynamic =/ "ANNOTATION" SP + # ( "(" entry-att *(SP entry-att) ")" / + # "(" entry *(SP entry) ")" ) + # RFC7162 (CONDSTORE extension): + # msg-att-dynamic =/ fetch-mod-resp + # fetch-mod-resp = "MODSEQ" SP "(" permsg-modsequence ")" + # RFC8970 (PREVIEW extension): + # msg-att-dynamic =/ "PREVIEW" SP nstring + # + # RFC3501: + # msg-att-static = "ENVELOPE" SP envelope / + # "INTERNALDATE" SP date-time / + # "RFC822" [".HEADER" / ".TEXT"] SP nstring / + # "RFC822.SIZE" SP number / + # "BODY" ["STRUCTURE"] SP body / + # "BODY" section ["<" number ">"] SP nstring / + # "UID" SP uniqueid + # RFC3516 (BINARY extension): + # msg-att-static =/ "BINARY" section-binary SP (nstring / literal8) + # / "BINARY.SIZE" section-binary SP number + # RFC8514 (SAVEDATE extension): + # msg-att-static =/ "SAVEDATE" SP (date-time / nil) + # RFC8474 (OBJECTID extension): + # msg-att-static =/ fetch-emailid-resp / fetch-threadid-resp + # fetch-emailid-resp = "EMAILID" SP "(" objectid ")" + # fetch-threadid-resp = "THREADID" SP ( "(" objectid ")" / nil ) + # RFC9051: + # msg-att-static = "ENVELOPE" SP envelope / + # "INTERNALDATE" SP date-time / + # "RFC822.SIZE" SP number64 / + # "BODY" ["STRUCTURE"] SP body / + # "BODY" section ["<" number ">"] SP nstring / + # "BINARY" section-binary SP (nstring / literal8) / + # "BINARY.SIZE" section-binary SP number / + # "UID" SP uniqueid + # + # Re https://www.rfc-editor.org/errata/eid7246, I'm adding "offset" to the + # official "BINARY" ABNF, like so: + # + # msg-att-static =/ "BINARY" section-binary ["<" number ">"] SP + # (nstring / literal8) + def msg_att(n) + lpar + attr = {} + while true + name = msg_att__label; SP! + val = + case name + when "UID" then uniqueid + when "FLAGS" then flag_list + when "BODY" then body + when /\ABODY\[/ni then nstring + when "BODYSTRUCTURE" then body + when "ENVELOPE" then envelope + when "INTERNALDATE" then date_time + when "RFC822.SIZE" then number64 + when /\ABINARY\[/ni then nstring8 # BINARY, IMAP4rev2 + when /\ABINARY\.SIZE\[/ni then number # BINARY, IMAP4rev2 + when "RFC822" then nstring # not in rev2 + when "RFC822.HEADER" then nstring # not in rev2 + when "RFC822.TEXT" then nstring # not in rev2 + when "MODSEQ" then parens__modseq # CONDSTORE + when "EMAILID" then parens__objectid # OBJECTID + when "THREADID" then nparens__objectid # OBJECTID + when "X-GM-MSGID" then x_gm_id # GMail + when "X-GM-THRID" then x_gm_id # GMail + when "X-GM-LABELS" then x_gm_labels # GMail + else parse_error("unknown attribute `%s' for {%d}", name, n) + end + attr[name] = val + break unless SP? + break if lookahead_rpar? + end + rpar + attr + end + + # appends "[section]" and "" to the base label + def msg_att__label + case (name = tagged_ext_label) + when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni + # ignoring "[]" fixes https://bugs.ruby-lang.org/issues/5620 + lbra? and rbra + when "BODY" + peek_lbra? and name << section and + peek_str?("<") and name << gt__number__lt # partial + when "BINARY", "BINARY.SIZE" + name << section_binary + # see https://www.rfc-editor.org/errata/eid7246 and the note above + peek_str?("<") and name << gt__number__lt # partial + end + name + end + + # this represents the partial size for BODY or BINARY + alias gt__number__lt atom + + # RFC3501 & RFC9051: + # envelope = "(" env-date SP env-subject SP env-from SP + # env-sender SP env-reply-to SP env-to SP env-cc SP + # env-bcc SP env-in-reply-to SP env-message-id ")" + def envelope + @lex_state = EXPR_DATA + lpar; date = env_date + SP!; subject = env_subject + SP!; from = env_from + SP!; sender = env_sender + SP!; reply_to = env_reply_to + SP!; to = env_to + SP!; cc = env_cc + SP!; bcc = env_bcc + SP!; in_reply_to = env_in_reply_to + SP!; message_id = env_message_id + rpar + Envelope.new(date, subject, from, sender, reply_to, + to, cc, bcc, in_reply_to, message_id) + ensure + @lex_state = EXPR_BEG + end + + # env-date = nstring + # env-subject = nstring + # env-in-reply-to = nstring + # env-message-id = nstring + alias env_date nstring + alias env_subject nstring + alias env_in_reply_to nstring + alias env_message_id nstring + + # env-from = "(" 1*address ")" / nil + # env-sender = "(" 1*address ")" / nil + # env-reply-to = "(" 1*address ")" / nil + # env-to = "(" 1*address ")" / nil + # env-cc = "(" 1*address ")" / nil + # env-bcc = "(" 1*address ")" / nil + def nlist__address + return if NIL? + lpar; list = [address]; list << address until (quirky_SP?; rpar?) + list + end + + alias env_from nlist__address + alias env_sender nlist__address + alias env_reply_to nlist__address + alias env_to nlist__address + alias env_cc nlist__address + alias env_bcc nlist__address + + # Used when servers erroneously send an extra SP. + # + # As of 2023-11-28, Outlook.com (still) sends SP + # between +address+ in env-* lists. + alias quirky_SP? SP? + + # date-time = DQUOTE date-day-fixed "-" date-month "-" date-year + # SP time SP zone DQUOTE + alias date_time quoted + alias ndatetime nquoted + + # RFC-3501 & RFC-9051: + # body = "(" (body-type-1part / body-type-mpart) ")" + def body + @lex_state = EXPR_DATA + lpar; result = peek_lpar? ? body_type_mpart : body_type_1part; rpar + result + ensure + @lex_state = EXPR_BEG + end + alias lookahead_body? lookahead_lpar? + + # RFC-3501 & RFC9051: + # body-type-1part = (body-type-basic / body-type-msg / body-type-text) + # [SP body-ext-1part] + def body_type_1part + # This regexp peek is a performance optimization. + # The lookahead fallback would work fine too. + m = peek_re(/\G(?: + (? "TEXT" \s "[^"]+" ) + |(? "MESSAGE" \s "(?:RFC822|GLOBAL)" ) + |(? "[^"]+" \s "[^"]+" ) + |(? "MIXED" ) + )/nix) + choice = m&.named_captures&.compact&.keys&.first + # In practice, the following line should never be used. But the ABNF + # *does* allow literals, and this will handle them. + choice ||= lookahead_case_insensitive__string! + case choice + when "BASIC" then body_type_basic # => BodyTypeBasic + when "MESSAGE" then body_type_msg # => BodyTypeMessage | BodyTypeBasic + when "TEXT" then body_type_text # => BodyTypeText + when "MIXED" then body_type_mixed # => BodyTypeMultipart (server bug) + else body_type_basic # might be a bug; server's or ours? + end + end + + # RFC-3501 & RFC9051: + # body-type-basic = media-basic SP body-fields + def body_type_basic + type = media_basic # n.b. "basic" type isn't enforced here + if lookahead_rpar? then return BodyTypeBasic.new(*type) end # invalid + SP!; flds = body_fields + SP? and exts = body_ext_1part + BodyTypeBasic.new(*type, *flds, *exts) + end + + # RFC-3501 & RFC-9051: + # body-type-text = media-text SP body-fields SP body-fld-lines + def body_type_text + type = media_text + SP!; flds = body_fields + SP!; lines = body_fld_lines + SP? and exts = body_ext_1part + BodyTypeText.new(*type, *flds, lines, *exts) + end + + # RFC-3501 & RFC-9051: + # body-type-msg = media-message SP body-fields SP envelope + # SP body SP body-fld-lines + def body_type_msg + # n.b. "message/rfc822" type isn't enforced here + type = media_message + SP!; flds = body_fields + + # Sometimes servers send body-type-basic when body-type-msg should be. + # E.g: when a message/rfc822 part has "Content-Disposition: attachment". + # + # * SP "(" --> SP envelope --> continue as body-type-msg + # * ")" --> no body-ext-1part --> completed body-type-basic + # * SP nstring --> SP body-fld-md5 + # --> SP body-ext-1part --> continue as body-type-basic + # + # It's probably better to return BodyTypeBasic---even for + # "message/rfc822"---than BodyTypeMessage with invalid fields. + unless peek_str?(" (") + SP? and exts = body_ext_1part + return BodyTypeBasic.new(*type, *flds, *exts) + end + + SP!; env = envelope + SP!; bdy = body + SP!; lines = body_fld_lines + SP? and exts = body_ext_1part + BodyTypeMessage.new(*type, *flds, env, bdy, lines, *exts) + end + + # This is a malformed body-type-mpart with no subparts. + def body_type_mixed + # warn "malformed body-type-mpart: multipart/mixed with no parts." + type = media_subtype # => "MIXED" + SP? and exts = body_ext_mpart + BodyTypeMultipart.new("MULTIPART", type, nil, *exts) + end + + # RFC-3501 & RFC-9051: + # body-type-mpart = 1*body SP media-subtype + # [SP body-ext-mpart] + def body_type_mpart + parts = [body]; parts << body until SP?; msubtype = media_subtype + SP? and exts = body_ext_mpart + BodyTypeMultipart.new("MULTIPART", msubtype, parts, *exts) + end + + # n.b. this handles both type and subtype + # + # RFC-3501 vs RFC-9051: + # media-basic = ((DQUOTE ("APPLICATION" / "AUDIO" / "IMAGE" / + # "MESSAGE" / + # "VIDEO") DQUOTE) / string) SP media-subtype + # media-basic = ((DQUOTE ("APPLICATION" / "AUDIO" / "IMAGE" / + # "FONT" / "MESSAGE" / "MODEL" / + # "VIDEO") DQUOTE) / string) SP media-subtype + # + # media-message = DQUOTE "MESSAGE" DQUOTE SP + # DQUOTE "RFC822" DQUOTE + # media-message = DQUOTE "MESSAGE" DQUOTE SP + # DQUOTE ("RFC822" / "GLOBAL") DQUOTE + # + # RFC-3501 & RFC-9051: + # media-text = DQUOTE "TEXT" DQUOTE SP media-subtype + # media-subtype = string + def media_type + mtype = case_insensitive__string + SP? or return mtype, nil # ??? quirky! + msubtype = media_subtype + return mtype, msubtype + end + + # TODO: check types + alias media_basic media_type # */* --- catchall + alias media_message media_type # message/rfc822, message/global + alias media_text media_type # text/* + + alias media_subtype case_insensitive__string + + # RFC-3501 & RFC-9051: + # body-fields = body-fld-param SP body-fld-id SP body-fld-desc SP + # body-fld-enc SP body-fld-octets + def body_fields + fields = [] + fields << body_fld_param; SP! + fields << body_fld_id; SP! + fields << body_fld_desc; SP! + fields << body_fld_enc; SP! + fields << body_fld_octets + fields + end + + # RFC3501, RFC9051: + # body-fld-param = "(" string SP string *(SP string SP string) ")" / nil + def body_fld_param + return if NIL? + param = {} + lpar + name = case_insensitive__string; SP!; param[name] = string + while SP? + name = case_insensitive__string; SP!; param[name] = string + end + rpar + param + end + + # RFC2060 + # body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp + # [SPACE body_fld_lang + # [SPACE 1#body_extension]]] + # ;; MUST NOT be returned on non-extensible + # ;; "BODY" fetch + # RFC3501 & RFC9051 + # body-ext-1part = body-fld-md5 [SP body-fld-dsp [SP body-fld-lang + # [SP body-fld-loc *(SP body-extension)]]] + # ; MUST NOT be returned on non-extensible + # ; "BODY" fetch + def body_ext_1part + fields = []; fields << body_fld_md5 + SP? or return fields; fields << body_fld_dsp + SP? or return fields; fields << body_fld_lang + SP? or return fields; fields << body_fld_loc + SP? or return fields; fields << body_extensions + fields + end + + # RFC-2060: + # body_ext_mpart = body_fld_param [SP body_fld_dsp SP body_fld_lang + # [SP 1#body_extension]] + # ;; MUST NOT be returned on non-extensible + # ;; "BODY" fetch + # RFC-3501 & RFC-9051: + # body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang + # [SP body-fld-loc *(SP body-extension)]]] + # ; MUST NOT be returned on non-extensible + # ; "BODY" fetch + def body_ext_mpart + fields = []; fields << body_fld_param + SP? or return fields; fields << body_fld_dsp + SP? or return fields; fields << body_fld_lang + SP? or return fields; fields << body_fld_loc + SP? or return fields; fields << body_extensions + fields + end + + alias body_fld_desc nstring + alias body_fld_id nstring + alias body_fld_loc nstring + alias body_fld_lines number64 # number in 3501, number64 in 9051 + alias body_fld_md5 nstring + alias body_fld_octets number + + # RFC-3501 & RFC-9051: + # body-fld-enc = (DQUOTE ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ + # "QUOTED-PRINTABLE") DQUOTE) / string + alias body_fld_enc case_insensitive__string + + # body-fld-dsp = "(" string SP body-fld-param ")" / nil + def body_fld_dsp + return if NIL? + lpar; dsp_type = case_insensitive__string + SP!; param = body_fld_param + rpar + ContentDisposition.new(dsp_type, param) + end + + # body-fld-lang = nstring / "(" string *(SP string) ")" + def body_fld_lang + if lpar? + result = [case_insensitive__string] + result << case_insensitive__string while SP? + rpar + result + else + case_insensitive__nstring + end + end + + # body-extension *(SP body-extension) + def body_extensions + result = [] + result << body_extension; while SP? do result << body_extension end + result + end + + # body-extension = nstring / number / number64 / + # "(" body-extension *(SP body-extension) ")" + # ; Future expansion. Client implementations + # ; MUST accept body-extension fields. Server + # ; implementations MUST NOT generate + # ; body-extension fields except as defined by + # ; future Standard or Standards Track + # ; revisions of this specification. + def body_extension + if (uint = number64?) then uint + elsif lpar? then exts = body_extensions; rpar; exts + else nstring + end + end + + # section = "[" [section-spec] "]" + def section + str = +lbra + str << section_spec unless peek_rbra? + str << rbra + end + + # section-binary = "[" [section-part] "]" + def section_binary + str = +lbra + str << section_part unless peek_rbra? + str << rbra + end + + # section-spec = section-msgtext / (section-part ["." section-text]) + # section-msgtext = "HEADER" / + # "HEADER.FIELDS" [".NOT"] SP header-list / + # "TEXT" + # ; top-level or MESSAGE/RFC822 or + # ; MESSAGE/GLOBAL part + # section-part = nz-number *("." nz-number) + # ; body part reference. + # ; Allows for accessing nested body parts. + # section-text = section-msgtext / "MIME" + # ; text other than actual body part (headers, + # ; etc.) + # + # n.b: we could "cheat" here and just grab all text inside the brackets, + # but literals would need special treatment. + def section_spec + str = "".b + str << atom # grabs everything up to "SP header-list" or "]" + str << " " << header_list if SP? + str + end + + # header-list = "(" header-fld-name *(SP header-fld-name) ")" + def header_list + str = +"" + str << lpar << header_fld_name + str << " " << header_fld_name while SP? + str << rpar + end + + # section-part = nz-number *("." nz-number) + # ; body part reference. + # ; Allows for accessing nested body parts. + alias section_part atom + + # RFC3501 & RFC9051: + # header-fld-name = astring + # + # NOTE: Previously, Net::IMAP recreated the raw original source string. + # Now, it grabs the raw encoded value using @str and @pos. A future + # version may simply return the decoded astring value. Although that is + # technically incompatible, it should almost never make a difference: all + # standard header field names are valid atoms: + # + # https://www.iana.org/assignments/message-headers/message-headers.xhtml + # + # Although RFC3501 allows any astring, RFC5322-valid header names are one + # or more of the printable US-ASCII characters, except SP and colon. So + # empty string isn't valid, and literals aren't needed and should not be + # used. This is explicitly unchanged by [I18N-HDRS] (RFC6532). + # + # RFC5233: + # optional-field = field-name ":" unstructured CRLF + # field-name = 1*ftext + # ftext = %d33-57 / ; Printable US-ASCII + # %d59-126 ; characters not including + # ; ":". + def header_fld_name + assert_no_lookahead + start = @pos + astring + @str[start...@pos - 1] + end + + # mailbox-data = "FLAGS" SP flag-list / "LIST" SP mailbox-list / + # "LSUB" SP mailbox-list / "SEARCH" *(SP nz-number) / + # "STATUS" SP mailbox SP "(" [status-att-list] ")" / + # number SP "EXISTS" / number SP "RECENT" + + def mailbox_data__flags + name = label("FLAGS") + SP! + UntaggedResponse.new(name, flag_list, @str) + end + + def mailbox_data__list + name = label_in("LIST", "LSUB", "XLIST") + SP! + UntaggedResponse.new(name, mailbox_list, @str) + end + alias mailbox_data__lsub mailbox_data__list + alias mailbox_data__xlist mailbox_data__list + + # mailbox-list = "(" [mbx-list-flags] ")" SP + # (DQUOTE QUOTED-CHAR DQUOTE / nil) SP mailbox + # [SP mbox-list-extended] + # ; This is the list information pointed to by the ABNF + # ; item "mailbox-data", which is defined above + def mailbox_list + lpar; attr = peek_rpar? ? [] : mbx_list_flags; rpar + SP!; delim = nquoted + SP!; name = mailbox + # TODO: mbox-list-extended + MailboxList.new(attr, delim, name) + end + + def quota_response + # If quota never established, get back + # `NO Quota root does not exist'. + # If quota removed, get `()' after the + # folder spec with no mention of `STORAGE'. + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + mailbox = astring + match(T_SPACE) + match(T_LPAR) + token = lookahead + case token.symbol + when T_RPAR + shift_token + data = MailboxQuota.new(mailbox, nil, nil) + return UntaggedResponse.new(name, data, @str) + when T_ATOM + shift_token + match(T_SPACE) + token = match(T_NUMBER) + usage = token.value + match(T_SPACE) + token = match(T_NUMBER) + quota = token.value + match(T_RPAR) + data = MailboxQuota.new(mailbox, usage, quota) + return UntaggedResponse.new(name, data, @str) + else + parse_error("unexpected token %s", token.symbol) + end + end + + def quotaroot_response + # Similar to getquota, but only admin can use getquota. + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + mailbox = astring + quotaroots = [] + while true + token = lookahead + break unless token.symbol == T_SPACE + shift_token + quotaroots.push(astring) + end + data = MailboxQuotaRoot.new(mailbox, quotaroots) + return UntaggedResponse.new(name, data, @str) + end + + # acl-data = "ACL" SP mailbox *(SP identifier SP rights) + def acl_data + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + mailbox = astring + data = [] + token = lookahead + if token.symbol == T_SPACE + shift_token + while true + token = lookahead + case token.symbol + when T_CRLF + break + when T_SPACE + shift_token + end + user = astring + match(T_SPACE) + rights = astring + data.push(MailboxACLItem.new(user, rights, mailbox)) + end + end + return UntaggedResponse.new(name, data, @str) + end + + # RFC3501: + # mailbox-data = "SEARCH" *(SP nz-number) / ... + # RFC5256: SORT + # sort-data = "SORT" *(SP nz-number) + # RFC7162: CONDSTORE, QRESYNC + # mailbox-data =/ "SEARCH" [1*(SP nz-number) SP + # search-sort-mod-seq] + # sort-data = "SORT" [1*(SP nz-number) SP + # search-sort-mod-seq] + # ; Updates the SORT response from RFC 5256. + # search-sort-mod-seq = "(" "MODSEQ" SP mod-sequence-value ")" + # RFC9051: + # mailbox-data = obsolete-search-response / ... + # obsolete-search-response = "SEARCH" *(SP nz-number) + def mailbox_data__search + name = label_in("SEARCH", "SORT") + data = [] + while _ = SP? && nz_number? do data << _ end + if lpar? + label("MODSEQ"); SP! + modseq = mod_sequence_value + rpar + end + data = SearchResult.new(data, modseq: modseq) + UntaggedResponse.new(name, data, @str) + end + alias sort_data mailbox_data__search + + # RFC5256: THREAD + # thread-data = "THREAD" [SP 1*thread-list] + def thread_data + name = label("THREAD") + threads = [] + if SP? + threads << thread_list while lookahead_thread_list? + end + UntaggedResponse.new(name, threads, @str) + end + + alias lookahead_thread_list? lookahead_lpar? + alias lookahead_thread_nested? lookahead_thread_list? + + # RFC5256: THREAD + # thread-list = "(" (thread-members / thread-nested) ")" + def thread_list + lpar + thread = if lookahead_thread_nested? + ThreadMember.new(nil, thread_nested) + else + thread_members + end + rpar + thread + end + + # RFC5256: THREAD + # thread-members = nz-number *(SP nz-number) [SP thread-nested] + def thread_members + members = [] + members << nz_number # thread root + while SP? + case lookahead!(T_NUMBER, T_LPAR).symbol + when T_NUMBER then members << nz_number + else nested = thread_nested; break + end + end + members.reverse.inject(nested || []) {|subthreads, number| + [ThreadMember.new(number, subthreads)] + }.first + end + + # RFC5256: THREAD + # thread-nested = 2*thread-list + def thread_nested + nested = [thread_list, thread_list] + while lookahead_thread_list? do nested << thread_list end + nested + end + + # mailbox-data =/ "STATUS" SP mailbox SP "(" [status-att-list] ")" + def mailbox_data__status + resp_name = label("STATUS"); SP! + mbox_name = mailbox; SP! + lpar; attr = status_att_list; rpar + UntaggedResponse.new(resp_name, StatusData.new(mbox_name, attr), @str) + end + + # RFC3501 + # status-att-list = status-att SP number *(SP status-att SP number) + # RFC4466, RFC9051, and RFC3501 Errata + # status-att-list = status-att-val *(SP status-att-val) + def status_att_list + attrs = [status_att_val] + while SP? do attrs << status_att_val end + attrs.to_h + end + + # RFC3501 Errata: + # status-att-val = ("MESSAGES" SP number) / ("RECENT" SP number) / + # ("UIDNEXT" SP nz-number) / ("UIDVALIDITY" SP nz-number) / + # ("UNSEEN" SP number) + # RFC4466: + # status-att-val = ("MESSAGES" SP number) / + # ("RECENT" SP number) / + # ("UIDNEXT" SP nz-number) / + # ("UIDVALIDITY" SP nz-number) / + # ("UNSEEN" SP number) + # ;; Extensions to the STATUS responses + # ;; should extend this production. + # ;; Extensions should use the generic + # ;; syntax defined by tagged-ext. + # RFC9051: + # status-att-val = ("MESSAGES" SP number) / + # ("UIDNEXT" SP nz-number) / + # ("UIDVALIDITY" SP nz-number) / + # ("UNSEEN" SP number) / + # ("DELETED" SP number) / + # ("SIZE" SP number64) + # ; Extensions to the STATUS responses + # ; should extend this production. + # ; Extensions should use the generic + # ; syntax defined by tagged-ext. + # RFC7162: + # status-att-val =/ "HIGHESTMODSEQ" SP mod-sequence-valzer + # ;; Extends non-terminal defined in [RFC4466]. + # ;; Value 0 denotes that the mailbox doesn't + # ;; support persistent mod-sequences + # ;; as described in Section 3.1.2.2. + # RFC7889: + # status-att-val =/ "APPENDLIMIT" SP (number / nil) + # ;; status-att-val is defined in RFC 4466 + # RFC8438: + # status-att-val =/ "SIZE" SP number64 + # RFC8474: + # status-att-val =/ "MAILBOXID" SP "(" objectid ")" + # ; follows tagged-ext production from [RFC4466] + def status_att_val + key = tagged_ext_label + SP! + val = + case key + when "MESSAGES" then number # RFC3501, RFC9051 + when "UNSEEN" then number # RFC3501, RFC9051 + when "DELETED" then number # RFC3501, RFC9051 + when "UIDNEXT" then nz_number # RFC3501, RFC9051 + when "UIDVALIDITY" then nz_number # RFC3501, RFC9051 + when "RECENT" then number # RFC3501 (obsolete) + when "SIZE" then number64 # RFC8483, RFC9051 + when "HIGHESTMODSEQ" then mod_sequence_valzer # RFC7162 + when "MAILBOXID" then parens__objectid # RFC8474 + else + number? || ExtensionData.new(tagged_ext_val) + end + [key, val] + end + + # The presence of "IMAP4rev1" or "IMAP4rev2" is unenforced here. + # The grammar rule is used by both response-data and resp-text-code. + # But this method only returns UntaggedResponse (response-data). + # + # RFC3501: + # capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1" + # *(SP capability) + # RFC9051: + # capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev2" + # *(SP capability) + def capability_data__untagged + UntaggedResponse.new label("CAPABILITY"), capability__list, @str + end + + # enable-data = "ENABLED" *(SP capability) + def enable_data + UntaggedResponse.new label("ENABLED"), capability__list, @str + end + + # As a workaround for buggy servers, allow a trailing SP: + # *(SP capability) [SP] + def capability__list + list = []; while SP? && (capa = capability?) do list << capa end; list + end + + alias resp_code__capability capability__list + + # capability = ("AUTH=" auth-type) / atom + # ; New capabilities MUST begin with "X" or be + # ; registered with IANA as standard or + # ; standards-track + alias capability case_insensitive__atom + alias capability? case_insensitive__atom? + + def id_response + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + token = match(T_LPAR, T_NIL) + if token.symbol == T_NIL + return UntaggedResponse.new(name, nil, @str) + else + data = {} + while true + token = lookahead + case token.symbol + when T_RPAR + shift_token + break + when T_SPACE + shift_token + next + else + key = string + match(T_SPACE) + val = nstring + data[key] = val + end + end + return UntaggedResponse.new(name, data, @str) + end + end + + # namespace-response = "NAMESPACE" SP namespace + # SP namespace SP namespace + # ; The first Namespace is the Personal Namespace(s). + # ; The second Namespace is the Other Users' + # ; Namespace(s). + # ; The third Namespace is the Shared Namespace(s). + def namespace_response + name = label("NAMESPACE") + @lex_state = EXPR_DATA + data = Namespaces.new((SP!; namespace), + (SP!; namespace), + (SP!; namespace)) + UntaggedResponse.new(name, data, @str) + ensure + @lex_state = EXPR_BEG + end + + # namespace = nil / "(" 1*namespace-descr ")" + def namespace + NIL? and return [] + lpar + list = [namespace_descr] + list << namespace_descr until rpar? + list + end + + # namespace-descr = "(" string SP + # (DQUOTE QUOTED-CHAR DQUOTE / nil) + # [namespace-response-extensions] ")" + def namespace_descr + lpar + prefix = string; SP! + delimiter = nquoted # n.b: should only accept single char + extensions = namespace_response_extensions + rpar + Namespace.new(prefix, delimiter, extensions) + end + + # namespace-response-extensions = *namespace-response-extension + # namespace-response-extension = SP string SP + # "(" string *(SP string) ")" + def namespace_response_extensions + data = {} + while SP? + name = string; SP! + lpar + data[name] ||= [] + data[name] << string + data[name] << string while SP? + rpar + end + data + end + + # TEXT-CHAR = + # RFC3501: + # text = 1*TEXT-CHAR + # RFC9051: + # text = 1*(TEXT-CHAR / UTF8-2 / UTF8-3 / UTF8-4) + # ; Non-ASCII text can only be returned + # ; after ENABLE IMAP4rev2 command + def text + match_re(TEXT_REGEXP, "text")[0].force_encoding("UTF-8") + end + + # an "accept" versiun of #text + def text? + accept_re(TEXT_REGEXP)&.[](0)&.force_encoding("UTF-8") + end + + # RFC3501: + # resp-text = ["[" resp-text-code "]" SP] text + # RFC9051: + # resp-text = ["[" resp-text-code "]" SP] [text] + # + # We leniently re-interpret this as + # resp-text = ["[" resp-text-code "]" [SP [text]] / [text] + def resp_text + if lbra? + code = resp_text_code; rbra + ResponseText.new(code, SP? && text? || "") + else + ResponseText.new(nil, text? || "") + end + end + + # RFC3501 (See https://www.rfc-editor.org/errata/rfc3501): + # resp-text-code = "ALERT" / + # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] / + # capability-data / "PARSE" / + # "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" / + # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / + # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number / + # "UNSEEN" SP nz-number / + # atom [SP 1*] + # capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev1" + # *(SP capability) + # + # RFC5530: + # resp-text-code =/ "UNAVAILABLE" / "AUTHENTICATIONFAILED" / + # "AUTHORIZATIONFAILED" / "EXPIRED" / + # "PRIVACYREQUIRED" / "CONTACTADMIN" / "NOPERM" / + # "INUSE" / "EXPUNGEISSUED" / "CORRUPTION" / + # "SERVERBUG" / "CLIENTBUG" / "CANNOT" / + # "LIMIT" / "OVERQUOTA" / "ALREADYEXISTS" / + # "NONEXISTENT" + # RFC9051: + # resp-text-code = "ALERT" / + # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] / + # capability-data / "PARSE" / + # "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" / + # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / + # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number / + # resp-code-apnd / resp-code-copy / "UIDNOTSTICKY" / + # "UNAVAILABLE" / "AUTHENTICATIONFAILED" / + # "AUTHORIZATIONFAILED" / "EXPIRED" / + # "PRIVACYREQUIRED" / "CONTACTADMIN" / "NOPERM" / + # "INUSE" / "EXPUNGEISSUED" / "CORRUPTION" / + # "SERVERBUG" / "CLIENTBUG" / "CANNOT" / + # "LIMIT" / "OVERQUOTA" / "ALREADYEXISTS" / + # "NONEXISTENT" / "NOTSAVED" / "HASCHILDREN" / + # "CLOSED" / + # "UNKNOWN-CTE" / + # atom [SP 1*] + # capability-data = "CAPABILITY" *(SP capability) SP "IMAP4rev2" + # *(SP capability) + # + # RFC4315 (UIDPLUS), RFC9051 (IMAP4rev2): + # resp-code-apnd = "APPENDUID" SP nz-number SP append-uid + # resp-code-copy = "COPYUID" SP nz-number SP uid-set SP uid-set + # resp-text-code =/ resp-code-apnd / resp-code-copy / "UIDNOTSTICKY" + # + # RFC7162 (CONDSTORE): + # resp-text-code =/ "HIGHESTMODSEQ" SP mod-sequence-value / + # "NOMODSEQ" / + # "MODIFIED" SP sequence-set + # RFC7162 (QRESYNC): + # resp-text-code =/ "CLOSED" + # + # RFC8474: OBJECTID + # resp-text-code =/ "MAILBOXID" SP "(" objectid ")" + def resp_text_code + name = resp_text_code__name + data = + case name + when "CAPABILITY" then resp_code__capability + when "PERMANENTFLAGS" then SP? ? flag_perm__list : [] + when "UIDNEXT" then SP!; nz_number + when "UIDVALIDITY" then SP!; nz_number + when "UNSEEN" then SP!; nz_number # rev1 only + when "APPENDUID" then SP!; resp_code_apnd__data # rev2, UIDPLUS + when "COPYUID" then SP!; resp_code_copy__data # rev2, UIDPLUS + when "BADCHARSET" then SP? ? charset__list : [] + when "ALERT", "PARSE", "READ-ONLY", "READ-WRITE", "TRYCREATE", + "UNAVAILABLE", "AUTHENTICATIONFAILED", "AUTHORIZATIONFAILED", + "EXPIRED", "PRIVACYREQUIRED", "CONTACTADMIN", "NOPERM", "INUSE", + "EXPUNGEISSUED", "CORRUPTION", "SERVERBUG", "CLIENTBUG", "CANNOT", + "LIMIT", "OVERQUOTA", "ALREADYEXISTS", "NONEXISTENT", "CLOSED", + "NOTSAVED", "UIDNOTSTICKY", "UNKNOWN-CTE", "HASCHILDREN" + when "NOMODSEQ" then nil # CONDSTORE + when "HIGHESTMODSEQ" then SP!; mod_sequence_value # CONDSTORE + when "MODIFIED" then SP!; sequence_set # CONDSTORE + when "MAILBOXID" then SP!; parens__objectid # RFC8474: OBJECTID + else + SP? and text_chars_except_rbra + end + ResponseCode.new(name, data) + end + + alias resp_text_code__name case_insensitive__atom + + # 1* + def text_chars_except_rbra + match_re(CTEXT_REGEXP, '1*')[0] + end + + # "(" charset *(SP charset) ")" + def charset__list + lpar; list = [charset]; while SP? do list << charset end; rpar; list + end + + # already matched: "APPENDUID" + # + # +UIDPLUS+ ABNF:: https://www.rfc-editor.org/rfc/rfc4315.html#section-4 + # resp-code-apnd = "APPENDUID" SP nz-number SP append-uid + # append-uid = uniqueid + # append-uid =/ uid-set + # ; only permitted if client uses [MULTIAPPEND] + # ; to append multiple messages. + # + # n.b, uniqueid ⊂ uid-set. To avoid inconsistent return types, we always + # match uid_set even if that returns a single-member array. + # + def resp_code_apnd__data + validity = number; SP! + dst_uids = uid_set # uniqueid ⊂ uid-set + UIDPlusData.new(validity, nil, dst_uids) + end + + # already matched: "COPYUID" + # + # resp-code-copy = "COPYUID" SP nz-number SP uid-set SP uid-set + def resp_code_copy__data + validity = number; SP! + src_uids = uid_set; SP! + dst_uids = uid_set + UIDPlusData.new(validity, src_uids, dst_uids) + end + + ADDRESS_REGEXP = /\G + \( (?: NIL | #{Patterns::QUOTED_rev2} ) # 1: NAME + \s (?: NIL | #{Patterns::QUOTED_rev2} ) # 2: ROUTE + \s (?: NIL | #{Patterns::QUOTED_rev2} ) # 3: MAILBOX + \s (?: NIL | #{Patterns::QUOTED_rev2} ) # 4: HOST + \) + /nix + + # address = "(" addr-name SP addr-adl SP addr-mailbox SP + # addr-host ")" + # addr-adl = nstring + # addr-host = nstring + # addr-mailbox = nstring + # addr-name = nstring + def address + if (match = accept_re(ADDRESS_REGEXP)) + # note that "NIL" isn't captured by the regexp + name, route, mailbox, host = match.captures + .map { Patterns.unescape_quoted _1 } + else # address may include literals + lpar; name = addr_name + SP!; route = addr_adl + SP!; mailbox = addr_mailbox + SP!; host = addr_host + rpar + end + Address.new(name, route, mailbox, host) + end + + alias addr_adl nstring + alias addr_host nstring + alias addr_mailbox nstring + alias addr_name nstring + + # flag-list = "(" [flag *(SP flag)] ")" + def flag_list + if (match = accept_re(Patterns::FLAG_LIST)) + match[1].split(nil) + .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 } + else + quirky__flag_list "flags-list" + end + end + + # "(" [flag-perm *(SP flag-perm)] ")" + def flag_perm__list + if (match = accept_re(Patterns::FLAG_PERM_LIST)) + match[1].split(nil) + .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 } + else + quirky__flag_list "PERMANENTFLAGS flag-perm list" + end + end + + # This allows illegal "]" in flag names (Gmail), + # or "\*" in a FLAGS response (greenmail). + def quirky__flag_list(name) + match_re(Patterns::QUIRKY_FLAGS_LIST, "quirks mode #{name}")[1] + .scan(Patterns::QUIRKY_FLAG) + .map! { _1.delete_prefix!("\\") ? _1.capitalize.to_sym : _1 } + end + + # See Patterns::MBX_LIST_FLAGS + def mbx_list_flags + match_re(Patterns::MBX_LIST_FLAGS, "mbx-list-flags")[1] + .split(nil) + .map! { _1.delete_prefix!("\\"); _1.capitalize.to_sym } + end + + # See https://developers.google.com/gmail/imap/imap-extensions + def x_gm_label; accept(T_BSLASH) ? atom.capitalize.to_sym : astring end + + # See https://developers.google.com/gmail/imap/imap-extensions + def x_gm_labels + lpar; return [] if rpar? + labels = [] + labels << x_gm_label + labels << x_gm_label while SP? + rpar + labels + end + + # See https://www.rfc-editor.org/errata/rfc3501 + # + # charset = atom / quoted + def charset; quoted? || atom end + + # RFC7162: + # mod-sequence-value = 1*DIGIT + # ;; Positive unsigned 63-bit integer + # ;; (mod-sequence) + # ;; (1 <= n <= 9,223,372,036,854,775,807). + alias mod_sequence_value nz_number64 + + # RFC7162: + # permsg-modsequence = mod-sequence-value + # ;; Per-message mod-sequence. + alias permsg_modsequence mod_sequence_value + + # RFC7162: + # mod-sequence-valzer = "0" / mod-sequence-value + alias mod_sequence_valzer number64 + + def parens__modseq; lpar; _ = permsg_modsequence; rpar; _ end + + # RFC8474: + # objectid = 1*255(ALPHA / DIGIT / "_" / "-") + # ; characters in object identifiers are case + # ; significant + alias objectid atom + + def parens__objectid; lpar; _ = objectid; rpar; _ end + def nparens__objectid; NIL? ? nil : parens__objectid end + + # RFC-4315 (UIDPLUS) or RFC9051 (IMAP4rev2): + # uid-set = (uniqueid / uid-range) *("," uid-set) + # uid-range = (uniqueid ":" uniqueid) + # ; two uniqueid values and all values + # ; between these two regardless of order. + # ; Example: 2:4 and 4:2 are equivalent. + # uniqueid = nz-number + # ; Strictly ascending + def uid_set + token = match(T_NUMBER, T_ATOM) + case token.symbol + when T_NUMBER then [Integer(token.value)] + when T_ATOM + token.value.split(",").flat_map {|range| + range = range.split(":").map {|uniqueid| Integer(uniqueid) } + range.size == 1 ? range : Range.new(range.min, range.max).to_a + } + end + end + + def nil_atom + match(T_NIL) + return nil + end + + SPACES_REGEXP = /\G */n + + # The RFC is very strict about this and usually we should be too. + # But skipping spaces is usually a safe workaround for buggy servers. + # + # This advances @pos directly so it's safe before changing @lex_state. + def accept_spaces + return false unless SP? + @str.index(SPACES_REGEXP, @pos) and + @pos = $~.end(0) + true + end + + def next_token + case @lex_state + when EXPR_BEG + if @str.index(BEG_REGEXP, @pos) + @pos = $~.end(0) + if $1 + return Token.new(T_SPACE, $+) + elsif $2 + len = $+.to_i + val = @str[@pos, len] + @pos += len + return Token.new(T_LITERAL8, val) + elsif $3 && $7 + # greedily match ATOM, prefixed with NUMBER, NIL, or PLUS. + return Token.new(T_ATOM, $3) + elsif $4 + return Token.new(T_NIL, $+) + elsif $5 + return Token.new(T_NUMBER, $+) + elsif $6 + return Token.new(T_PLUS, $+) + elsif $8 + # match ATOM, without a NUMBER, NIL, or PLUS prefix + return Token.new(T_ATOM, $+) + elsif $9 + return Token.new(T_QUOTED, Patterns.unescape_quoted($+)) + elsif $10 + return Token.new(T_LPAR, $+) + elsif $11 + return Token.new(T_RPAR, $+) + elsif $12 + return Token.new(T_BSLASH, $+) + elsif $13 + return Token.new(T_STAR, $+) + elsif $14 + return Token.new(T_LBRA, $+) + elsif $15 + return Token.new(T_RBRA, $+) + elsif $16 + len = $+.to_i + val = @str[@pos, len] + @pos += len + return Token.new(T_LITERAL, val) + elsif $17 + return Token.new(T_PERCENT, $+) + elsif $18 + return Token.new(T_CRLF, $+) + elsif $19 + return Token.new(T_EOF, $+) + else + parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid") + end + else + @str.index(/\S*/n, @pos) + parse_error("unknown token - %s", $&.dump) + end + when EXPR_DATA + if @str.index(DATA_REGEXP, @pos) + @pos = $~.end(0) + if $1 + return Token.new(T_SPACE, $+) + elsif $2 + return Token.new(T_NIL, $+) + elsif $3 + return Token.new(T_NUMBER, $+) + elsif $4 + return Token.new(T_QUOTED, Patterns.unescape_quoted($+)) + elsif $5 + len = $+.to_i + val = @str[@pos, len] + @pos += len + return Token.new(T_LITERAL, val) + elsif $6 + return Token.new(T_LPAR, $+) + elsif $7 + return Token.new(T_RPAR, $+) + else + parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid") + end + else + @str.index(/\S*/n, @pos) + parse_error("unknown token - %s", $&.dump) + end + else + parse_error("invalid @lex_state - %s", @lex_state.inspect) + end + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/anonymous_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/anonymous_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/anonymous_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/anonymous_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + module SASL + + # Authenticator for the "+ANONYMOUS+" SASL mechanism, as specified by + # RFC-4505[https://tools.ietf.org/html/rfc4505]. See + # Net::IMAP#authenticate. + class AnonymousAuthenticator + + # An optional token sent for the +ANONYMOUS+ mechanism., up to 255 UTF-8 + # characters in length. + # + # If it contains an "@" sign, the message must be a valid email address + # (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]). + # Email syntax is _not_ validated by AnonymousAuthenticator. + # + # Otherwise, it can be any UTF8 string which is permitted by the + # StringPrep::Trace profile. + attr_reader :anonymous_message + + # :call-seq: + # new(anonymous_message = "", **) -> authenticator + # new(anonymous_message: "", **) -> authenticator + # + # Creates an Authenticator for the "+ANONYMOUS+" SASL mechanism, as + # specified in RFC-4505[https://tools.ietf.org/html/rfc4505]. To use + # this, see Net::IMAP#authenticate or your client's authentication + # method. + # + # ==== Parameters + # + # * _optional_ #anonymous_message — a message to send to the server. + # + # Any other keyword arguments are silently ignored. + def initialize(anon_msg = nil, anonymous_message: nil, **) + message = (anonymous_message || anon_msg || "").to_str + @anonymous_message = StringPrep::Trace.stringprep_trace message + if (size = @anonymous_message&.length)&.> 255 + raise ArgumentError, + "anonymous_message is too long. (%d codepoints)" % [size] + end + @done = false + end + + # :call-seq: + # initial_response? -> true + # + # +ANONYMOUS+ can send an initial client response. + def initial_response?; true end + + # Returns #anonymous_message. + def process(_server_challenge_string) + anonymous_message + ensure + @done = true + end + + # Returns true when the initial client response was sent. + # + # The authentication should not succeed unless this returns true, but it + # does *not* indicate success. + def done?; @done end + + end + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/authentication_exchange.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/authentication_exchange.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/authentication_exchange.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/authentication_exchange.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module Net + class IMAP + module SASL + + # This API is *experimental*, and may change. + # + # TODO: catch exceptions in #process and send #cancel_response. + # TODO: raise an error if the command succeeds after being canceled. + # TODO: use with more clients, to verify the API can accommodate them. + # + # Create an AuthenticationExchange from a client adapter and a mechanism + # authenticator: + # def authenticate(mechanism, ...) + # authenticator = SASL.authenticator(mechanism, ...) + # SASL::AuthenticationExchange.new( + # sasl_adapter, mechanism, authenticator + # ).authenticate + # end + # + # private + # + # def sasl_adapter = MyClientAdapter.new(self, &method(:send_command)) + # + # Or delegate creation of the authenticator to ::build: + # def authenticate(...) + # SASL::AuthenticationExchange.build(sasl_adapter, ...) + # .authenticate + # end + # + # As a convenience, ::authenticate combines ::build and #authenticate: + # def authenticate(...) + # SASL::AuthenticationExchange.authenticate(sasl_adapter, ...) + # end + # + # Likewise, ClientAdapter#authenticate delegates to #authenticate: + # def authenticate(...) = sasl_adapter.authenticate(...) + # + class AuthenticationExchange + # Convenience method for build(...).authenticate + def self.authenticate(...) build(...).authenticate end + + # Use +registry+ to override the global Authenticators registry. + def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block) + authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block) + new(client, mechanism, authenticator, sasl_ir: sasl_ir) + end + + attr_reader :mechanism, :authenticator + + def initialize(client, mechanism, authenticator, sasl_ir: true) + @client = client + @mechanism = -mechanism.to_s.upcase.tr(?_, ?-) + @authenticator = authenticator + @sasl_ir = sasl_ir + @processed = false + end + + # Call #authenticate to execute an authentication exchange for #client + # using #authenticator. Authentication failures will raise an + # exception. Any exceptions other than those in RESPONSE_ERRORS will + # drop the connection. + def authenticate + client.run_command(mechanism, initial_response) { process _1 } + .tap { raise AuthenticationIncomplete, _1 unless done? } + rescue *client.response_errors + raise # but don't drop the connection + rescue + client.drop_connection + raise + rescue Exception # rubocop:disable Lint/RescueException + client.drop_connection! + raise + end + + def send_initial_response? + @sasl_ir && + authenticator.respond_to?(:initial_response?) && + authenticator.initial_response? && + client.sasl_ir_capable? && + client.auth_capable?(mechanism) + end + + def done? + authenticator.respond_to?(:done?) ? authenticator.done? : @processed + end + + private + + attr_reader :client + + def initial_response + return unless send_initial_response? + client.encode_ir authenticator.process nil + end + + def process(challenge) + client.encode authenticator.process client.decode challenge + ensure + @processed = true + end + + end + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/authenticators.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/authenticators.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/authenticators.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/authenticators.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module Net::IMAP::SASL + + # Registry for SASL authenticators + # + # Registered authenticators must respond to +#new+ or +#call+ (e.g. a class or + # a proc), receiving any credentials and options and returning an + # authenticator instance. The returned object represents a single + # authentication exchange and must not be reused for multiple + # authentication attempts. + # + # An authenticator instance object must respond to +#process+, receiving the + # server's challenge and returning the client's response. Optionally, it may + # also respond to +#initial_response?+ and +#done?+. When + # +#initial_response?+ returns +true+, +#process+ may be called the first + # time with +nil+. When +#done?+ returns +false+, the exchange is incomplete + # and an exception should be raised if the exchange terminates prematurely. + # + # See the source for PlainAuthenticator, XOAuth2Authenticator, and + # ScramSHA1Authenticator for examples. + class Authenticators + + # Create a new Authenticators registry. + # + # This class is usually not instantiated directly. Use SASL.authenticators + # to reuse the default global registry. + # + # When +use_defaults+ is +false+, the registry will start empty. When + # +use_deprecated+ is +false+, deprecated authenticators will not be + # included with the defaults. + def initialize(use_defaults: true, use_deprecated: true) + @authenticators = {} + return unless use_defaults + add_authenticator "Anonymous" + add_authenticator "External" + add_authenticator "OAuthBearer" + add_authenticator "Plain" + add_authenticator "Scram-SHA-1" + add_authenticator "Scram-SHA-256" + add_authenticator "XOAuth2" + return unless use_deprecated + add_authenticator "Login" # deprecated + add_authenticator "Cram-MD5" # deprecated + add_authenticator "Digest-MD5" # deprecated + end + + # Returns the names of all registered SASL mechanisms. + def names; @authenticators.keys end + + # :call-seq: + # add_authenticator(mechanism) + # add_authenticator(mechanism, authenticator_class) + # add_authenticator(mechanism, authenticator_proc) + # + # Registers an authenticator for #authenticator to use. +mechanism+ is the + # name of the + # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # implemented by +authenticator_class+ (for instance, "PLAIN"). + # + # If +mechanism+ refers to an existing authenticator, + # the old authenticator will be replaced. + # + # When only a single argument is given, the authenticator class will be + # lazily loaded from Net::IMAP::SASL::#{name}Authenticator (case is + # preserved and non-alphanumeric characters are removed.. + def add_authenticator(name, authenticator = nil) + key = -name.to_s.upcase.tr(?_, ?-) + authenticator ||= begin + class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym + auth_class = nil + ->(*creds, **props, &block) { + auth_class ||= Net::IMAP::SASL.const_get(class_name) + auth_class.new(*creds, **props, &block) + } + end + @authenticators[key] = authenticator + end + + # Removes the authenticator registered for +name+ + def remove_authenticator(name) + key = -name.to_s.upcase.tr(?_, ?-) + @authenticators.delete(key) + end + + def mechanism?(name) + key = -name.to_s.upcase.tr(?_, ?-) + @authenticators.key?(key) + end + + # :call-seq: + # authenticator(mechanism, ...) -> auth_session + # + # Builds an authenticator instance using the authenticator registered to + # +mechanism+. The returned object represents a single authentication + # exchange and must not be reused for multiple authentication + # attempts. + # + # All arguments (except +mechanism+) are forwarded to the registered + # authenticator's +#new+ or +#call+ method. Each authenticator must + # document its own arguments. + # + # [Note] + # This method is intended for internal use by connection protocol code + # only. Protocol client users should see refer to their client's + # documentation, e.g. Net::IMAP#authenticate. + def authenticator(mechanism, ...) + key = -mechanism.to_s.upcase.tr(?_, ?-) + auth = @authenticators.fetch(key) do + raise ArgumentError, 'unknown auth type - "%s"' % key + end + auth.respond_to?(:new) ? auth.new(...) : auth.call(...) + end + alias new authenticator + + end + +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/client_adapter.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/client_adapter.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/client_adapter.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/client_adapter.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module Net + class IMAP + module SASL + + # This API is *experimental*, and may change. + # + # TODO: use with more clients, to verify the API can accommodate them. + # + # An abstract base class for implementing a SASL authentication exchange. + # Different clients will each have their own adapter subclass, overridden + # to match their needs. + # + # Although the default implementations _may_ be sufficient, subclasses + # will probably need to override some methods. Additionally, subclasses + # may need to include a protocol adapter mixin, if the default + # ProtocolAdapters::Generic isn't sufficient. + class ClientAdapter + include ProtocolAdapters::Generic + + attr_reader :client, :command_proc + + # +command_proc+ can used to avoid exposing private methods on #client. + # It should run a command with the arguments sent to it, yield each + # continuation payload, respond to the server with the result of each + # yield, and return the result. Non-successful results *MUST* raise an + # exception. Exceptions in the block *MUST* cause the command to fail. + # + # Subclasses that override #run_command may use #command_proc for + # other purposes. + def initialize(client, &command_proc) + @client, @command_proc = client, command_proc + end + + # Delegates to AuthenticationExchange.authenticate. + def authenticate(...) AuthenticationExchange.authenticate(self, ...) end + + # Do the protocol and server both support an initial response? + def sasl_ir_capable?; client.sasl_ir_capable? end + + # Does the server advertise support for the mechanism? + def auth_capable?(mechanism); client.auth_capable?(mechanism) end + + # Runs the authenticate command with +mechanism+ and +initial_response+. + # When +initial_response+ is nil, an initial response must NOT be sent. + # + # Yields each continuation payload, responds to the server with the + # result of each yield, and returns the result. Non-successful results + # *MUST* raise an exception. Exceptions in the block *MUST* cause the + # command to fail. + # + # Subclasses that override this may use #command_proc differently. + def run_command(mechanism, initial_response = nil, &block) + command_proc or raise Error, "initialize with block or override" + args = [command_name, mechanism, initial_response].compact + command_proc.call(*args, &block) + end + + # Returns an array of server responses errors raised by run_command. + # Exceptions in this array won't drop the connection. + def response_errors; [] end + + # Drop the connection gracefully. + def drop_connection; client.drop_connection end + + # Drop the connection abruptly. + def drop_connection!; client.drop_connection! end + end + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/cram_md5_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/cram_md5_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/cram_md5_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/cram_md5_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +# Authenticator for the "+CRAM-MD5+" SASL mechanism, specified in +# RFC2195[https://tools.ietf.org/html/rfc2195]. See Net::IMAP#authenticate. +# +# == Deprecated +# +# +CRAM-MD5+ is obsolete and insecure. It is included for compatibility with +# existing servers. +# {draft-ietf-sasl-crammd5-to-historic}[https://tools.ietf.org/html/draft-ietf-sasl-crammd5-to-historic-00.html] +# recommends using +SCRAM-*+ or +PLAIN+ protected by TLS instead. +# +# Additionally, RFC8314[https://tools.ietf.org/html/rfc8314] discourage the use +# of cleartext and recommends TLS version 1.2 or greater be used for all +# traffic. With TLS +CRAM-MD5+ is okay, but so is +PLAIN+ +class Net::IMAP::SASL::CramMD5Authenticator + def initialize(user = nil, pass = nil, + authcid: nil, username: nil, + password: nil, secret: nil, + warn_deprecation: true, + **) + if warn_deprecation + warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM + end + require "digest/md5" + @user = authcid || username || user + @password = password || secret || pass + @done = false + end + + def initial_response?; false end + + def process(challenge) + digest = hmac_md5(challenge, @password) + return @user + " " + digest + ensure + @done = true + end + + def done?; @done end + + private + + def hmac_md5(text, key) + if key.length > 64 + key = Digest::MD5.digest(key) + end + + k_ipad = key + "\0" * (64 - key.length) + k_opad = key + "\0" * (64 - key.length) + for i in 0..63 + k_ipad[i] = (k_ipad[i].ord ^ 0x36).chr + k_opad[i] = (k_opad[i].ord ^ 0x5c).chr + end + + digest = Digest::MD5.digest(k_ipad + text) + + return Digest::MD5.hexdigest(k_opad + digest) + end + +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/digest_md5_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +# Net::IMAP authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified +# in RFC-2831[https://tools.ietf.org/html/rfc2831]. See Net::IMAP#authenticate. +# +# == Deprecated +# +# "+DIGEST-MD5+" has been deprecated by +# RFC-6331[https://tools.ietf.org/html/rfc6331] and should not be relied on for +# security. It is included for compatibility with existing servers. +class Net::IMAP::SASL::DigestMD5Authenticator + STAGE_ONE = :stage_one + STAGE_TWO = :stage_two + STAGE_DONE = :stage_done + private_constant :STAGE_ONE, :STAGE_TWO, :STAGE_DONE + + # Authentication identity: the identity that matches the #password. + # + # RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+. + # "Authentication identity" is the generic term used by + # RFC-4422[https://tools.ietf.org/html/rfc4422]. + # RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate + # this to +authcid+. + attr_reader :username + alias authcid username + + # A password or passphrase that matches the #username. + # + # The +password+ will be used to create the response digest. + attr_reader :password + + # Authorization identity: an identity to act as or on behalf of. The identity + # form is application protocol specific. If not provided or left blank, the + # server derives an authorization identity from the authentication identity. + # The server is responsible for verifying the client's credentials and + # verifying that the identity it associates with the client's authentication + # identity is allowed to act as (or on behalf of) the authorization identity. + # + # For example, an administrator or superuser might take on another role: + # + # imap.authenticate "DIGEST-MD5", "root", ->{passwd}, authzid: "user" + # + attr_reader :authzid + + # :call-seq: + # new(username, password, authzid = nil, **options) -> authenticator + # new(username:, password:, authzid: nil, **options) -> authenticator + # new(authcid:, password:, authzid: nil, **options) -> authenticator + # + # Creates an Authenticator for the "+DIGEST-MD5+" SASL mechanism. + # + # Called by Net::IMAP#authenticate and similar methods on other clients. + # + # ==== Parameters + # + # * #authcid ― Authentication identity that is associated with #password. + # + # #username ― An alias for +authcid+. + # + # * #password ― A password or passphrase associated with this #authcid. + # + # * _optional_ #authzid ― Authorization identity to act as or on behalf of. + # + # When +authzid+ is not set, the server should derive the authorization + # identity from the authentication identity. + # + # * _optional_ +warn_deprecation+ — Set to +false+ to silence the warning. + # + # Any other keyword arguments are silently ignored. + def initialize(user = nil, pass = nil, authz = nil, + username: nil, password: nil, authzid: nil, + authcid: nil, secret: nil, + warn_deprecation: true, **) + username = authcid || username || user or + raise ArgumentError, "missing username (authcid)" + password ||= secret || pass or raise ArgumentError, "missing password" + authzid ||= authz + if warn_deprecation + warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331." + # TODO: recommend SCRAM instead. + end + require "digest/md5" + require "strscan" + @username, @password, @authzid = username, password, authzid + @nc, @stage = {}, STAGE_ONE + end + + def initial_response?; false end + + # Responds to server challenge in two stages. + def process(challenge) + case @stage + when STAGE_ONE + @stage = STAGE_TWO + sparams = {} + c = StringScanner.new(challenge) + while c.scan(/(?:\s*,)?\s*(\w+)=("(?:[^\\"]|\\.)*"|[^,]+)\s*/) + k, v = c[1], c[2] + if v =~ /^"(.*)"$/ + v = $1 + if v =~ /,/ + v = v.split(',') + end + end + sparams[k] = v + end + + raise Net::IMAP::DataFormatError, "Bad Challenge: '#{challenge}'" unless c.eos? and sparams['qop'] + raise Net::IMAP::Error, "Server does not support auth (qop = #{sparams['qop'].join(',')})" unless sparams['qop'].include?("auth") + + response = { + :nonce => sparams['nonce'], + :username => @username, + :realm => sparams['realm'], + :cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]), + :'digest-uri' => 'imap/' + sparams['realm'], + :qop => 'auth', + :maxbuf => 65535, + :nc => "%08d" % nc(sparams['nonce']), + :charset => sparams['charset'], + } + + response[:authzid] = @authzid unless @authzid.nil? + + # now, the real thing + a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') ) + + a1 = [ a0, response.values_at(:nonce,:cnonce) ].join(':') + a1 << ':' + response[:authzid] unless response[:authzid].nil? + + a2 = "AUTHENTICATE:" + response[:'digest-uri'] + a2 << ":00000000000000000000000000000000" if response[:qop] and response[:qop] =~ /^auth-(?:conf|int)$/ + + response[:response] = Digest::MD5.hexdigest( + [ + Digest::MD5.hexdigest(a1), + response.values_at(:nonce, :nc, :cnonce, :qop), + Digest::MD5.hexdigest(a2) + ].join(':') + ) + + return response.keys.map {|key| qdval(key.to_s, response[key]) }.join(',') + when STAGE_TWO + @stage = STAGE_DONE + # if at the second stage, return an empty string + if challenge =~ /rspauth=/ + return '' + else + raise ResponseParseError, challenge + end + else + raise ResponseParseError, challenge + end + end + + def done?; @stage == STAGE_DONE end + + private + + def nc(nonce) + if @nc.has_key? nonce + @nc[nonce] = @nc[nonce] + 1 + else + @nc[nonce] = 1 + end + return @nc[nonce] + end + + # some responses need quoting + def qdval(k, v) + return if k.nil? or v.nil? + if %w"username authzid realm nonce cnonce digest-uri qop".include? k + v = v.gsub(/([\\"])/, "\\\1") + return '%s="%s"' % [k, v] + else + return '%s=%s' % [k, v] + end + end + +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/external_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/external_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/external_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/external_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + module SASL + + # Authenticator for the "+EXTERNAL+" SASL mechanism, as specified by + # RFC-4422[https://tools.ietf.org/html/rfc4422]. See + # Net::IMAP#authenticate. + # + # The EXTERNAL mechanism requests that the server use client credentials + # established external to SASL, for example by TLS certificate or IPsec. + class ExternalAuthenticator + + # Authorization identity: an identity to act as or on behalf of. The + # identity form is application protocol specific. If not provided or + # left blank, the server derives an authorization identity from the + # authentication identity. The server is responsible for verifying the + # client's credentials and verifying that the identity it associates + # with the client's authentication identity is allowed to act as (or on + # behalf of) the authorization identity. + # + # For example, an administrator or superuser might take on another role: + # + # imap.authenticate "PLAIN", "root", passwd, authzid: "user" + # + attr_reader :authzid + alias username authzid + + # :call-seq: + # new(authzid: nil, **) -> authenticator + # new(username: nil, **) -> authenticator + # new(username = nil, **) -> authenticator + # + # Creates an Authenticator for the "+EXTERNAL+" SASL mechanism, as + # specified in RFC-4422[https://tools.ietf.org/html/rfc4422]. To use + # this, see Net::IMAP#authenticate or your client's authentication + # method. + # + # ==== Parameters + # + # * _optional_ #authzid ― Authorization identity to act as or on behalf of. + # + # _optional_ #username ― An alias for #authzid. + # + # Note that, unlike some other authenticators, +username+ sets the + # _authorization_ identity and not the _authentication_ identity. The + # authentication identity is established for the client by the + # external credentials. + # + # Any other keyword parameters are quietly ignored. + def initialize(user = nil, authzid: nil, username: nil, **) + authzid ||= username || user + @authzid = authzid&.to_str&.encode "UTF-8" + if @authzid&.match?(/\u0000/u) # also validates UTF8 encoding + raise ArgumentError, "contains NULL" + end + @done = false + end + + # :call-seq: + # initial_response? -> true + # + # +EXTERNAL+ can send an initial client response. + def initial_response?; true end + + # Returns #authzid, or an empty string if there is no authzid. + def process(_) + authzid || "" + ensure + @done = true + end + + # Returns true when the initial client response was sent. + # + # The authentication should not succeed unless this returns true, but it + # does *not* indicate success. + def done?; @done end + + end + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/gs2_header.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/gs2_header.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/gs2_header.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/gs2_header.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + module SASL + + # Originally defined for the GS2 mechanism family in + # RFC5801[https://tools.ietf.org/html/rfc5801], + # several different mechanisms start with a GS2 header: + # * +GS2-*+ --- RFC5801[https://tools.ietf.org/html/rfc5801] + # * +SCRAM-*+ --- RFC5802[https://tools.ietf.org/html/rfc5802] + # (ScramAuthenticator) + # * +SAML20+ --- RFC6595[https://tools.ietf.org/html/rfc6595] + # * +OPENID20+ --- RFC6616[https://tools.ietf.org/html/rfc6616] + # * +OAUTH10A+ --- RFC7628[https://tools.ietf.org/html/rfc7628] + # * +OAUTHBEARER+ --- RFC7628[https://tools.ietf.org/html/rfc7628] + # (OAuthBearerAuthenticator) + # + # Classes that include this module must implement +#authzid+. + module GS2Header + NO_NULL_CHARS = /\A[^\x00]+\z/u.freeze # :nodoc: + + ## + # Matches {RFC5801 §4}[https://www.rfc-editor.org/rfc/rfc5801#section-4] + # +saslname+. The output from gs2_saslname_encode matches this Regexp. + RFC5801_SASLNAME = /\A(?:[^,=\x00]|=2C|=3D)+\z/u.freeze + + # The {RFC5801 §4}[https://www.rfc-editor.org/rfc/rfc5801#section-4] + # +gs2-header+, which prefixes the #initial_client_response. + # + # >>> + # Note: the actual GS2 header includes an optional flag to + # indicate that the GSS mechanism is not "standard", but since all of + # the SASL mechanisms using GS2 are "standard", we don't include that + # flag. A class for a nonstandard GSSAPI mechanism should prefix with + # "+F,+". + def gs2_header + "#{gs2_cb_flag},#{gs2_authzid}," + end + + # The {RFC5801 §4}[https://www.rfc-editor.org/rfc/rfc5801#section-4] + # +gs2-cb-flag+: + # + # "+n+":: The client doesn't support channel binding. + # "+y+":: The client does support channel binding + # but thinks the server does not. + # "+p+":: The client requires channel binding. + # The selected channel binding follows "+p=+". + # + # The default always returns "+n+". A mechanism that supports channel + # binding must override this method. + # + def gs2_cb_flag; "n" end + + # The {RFC5801 §4}[https://www.rfc-editor.org/rfc/rfc5801#section-4] + # +gs2-authzid+ header, when +#authzid+ is not empty. + # + # If +#authzid+ is empty or +nil+, an empty string is returned. + def gs2_authzid + return "" if authzid.nil? || authzid == "" + "a=#{gs2_saslname_encode(authzid)}" + end + + module_function + + # Encodes +str+ to match RFC5801_SASLNAME. + def gs2_saslname_encode(str) + str = str.encode("UTF-8") + # Regexp#match raises "invalid byte sequence" for invalid UTF-8 + NO_NULL_CHARS.match str or + raise ArgumentError, "invalid saslname: %p" % [str] + str + .gsub(?=, "=3D") + .gsub(?,, "=2C") + end + + end + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/login_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/login_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/login_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/login_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# Authenticator for the "+LOGIN+" SASL mechanism. See Net::IMAP#authenticate. +# +# +LOGIN+ authentication sends the password in cleartext. +# RFC3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable +# cleartext authentication until after TLS has been negotiated. +# RFC8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or +# greater be used for all traffic, and deprecate cleartext access ASAP. +LOGIN+ +# can be secured by TLS encryption. +# +# == Deprecated +# +# The {SASL mechanisms +# registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] +# marks "LOGIN" as obsoleted in favor of "PLAIN". It is included here for +# compatibility with existing servers. See +# {draft-murchison-sasl-login}[https://www.iana.org/go/draft-murchison-sasl-login] +# for both specification and deprecation. +class Net::IMAP::SASL::LoginAuthenticator + STATE_USER = :USER + STATE_PASSWORD = :PASSWORD + STATE_DONE = :DONE + private_constant :STATE_USER, :STATE_PASSWORD, :STATE_DONE + + def initialize(user = nil, pass = nil, + authcid: nil, username: nil, + password: nil, secret: nil, + warn_deprecation: true, + **) + if warn_deprecation + warn "WARNING: LOGIN SASL mechanism is deprecated. Use PLAIN instead." + end + @user = authcid || username || user + @password = password || secret || pass + @state = STATE_USER + end + + def initial_response?; false end + + def process(data) + case @state + when STATE_USER + @state = STATE_PASSWORD + return @user + when STATE_PASSWORD + @state = STATE_DONE + return @password + when STATE_DONE + raise ResponseParseError, data + end + end + + def done?; @state == STATE_DONE end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/oauthbearer_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/oauthbearer_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/oauthbearer_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/oauthbearer_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,199 @@ +# frozen_string_literal: true + +require_relative "gs2_header" + +module Net + class IMAP < Protocol + module SASL + + # Abstract base class for the SASL mechanisms defined in + # RFC7628[https://tools.ietf.org/html/rfc7628]: + # * OAUTHBEARER[rdoc-ref:OAuthBearerAuthenticator] + # (OAuthBearerAuthenticator) + # * OAUTH10A + class OAuthAuthenticator + include GS2Header + + # Authorization identity: an identity to act as or on behalf of. The + # identity form is application protocol specific. If not provided or + # left blank, the server derives an authorization identity from the + # authentication identity. The server is responsible for verifying the + # client's credentials and verifying that the identity it associates + # with the client's authentication identity is allowed to act as (or on + # behalf of) the authorization identity. + # + # For example, an administrator or superuser might take on another role: + # + # imap.authenticate "PLAIN", "root", passwd, authzid: "user" + # + attr_reader :authzid + alias username authzid + + # Hostname to which the client connected. (optional) + attr_reader :host + + # Service port to which the client connected. (optional) + attr_reader :port + + # HTTP method. (optional) + attr_reader :mthd + + # HTTP path data. (optional) + attr_reader :path + + # HTTP post data. (optional) + attr_reader :post + + # The query string. (optional) + attr_reader :qs + alias query qs + + # Stores the most recent server "challenge". When authentication fails, + # this may hold information about the failure reason, as JSON. + attr_reader :last_server_response + + # Creates an RFC7628[https://tools.ietf.org/html/rfc7628] OAuth + # authenticator. + # + # ==== Parameters + # + # See child classes for required parameter(s). The following parameters + # are all optional, but it is worth noting that application protocols + # are allowed to require #authzid (or other parameters, such as + # #host or #port) as are specific server implementations. + # + # * _optional_ #authzid ― Authorization identity to act as or on behalf of. + # + # _optional_ #username — An alias for #authzid. + # + # Note that, unlike some other authenticators, +username+ sets the + # _authorization_ identity and not the _authentication_ identity. The + # authentication identity is established for the client by the OAuth + # token. + # + # * _optional_ #host — Hostname to which the client connected. + # * _optional_ #port — Service port to which the client connected. + # * _optional_ #mthd — HTTP method + # * _optional_ #path — HTTP path data + # * _optional_ #post — HTTP post data + # * _optional_ #qs — HTTP query string + # + # _optional_ #query — An alias for #qs + # + # Any other keyword parameters are quietly ignored. + def initialize(authzid: nil, host: nil, port: nil, + username: nil, query: nil, + mthd: nil, path: nil, post: nil, qs: nil, **) + @authzid = authzid || username + @host = host + @port = port + @mthd = mthd + @path = path + @post = post + @qs = qs || query + @done = false + end + + # The {RFC7628 §3.1}[https://www.rfc-editor.org/rfc/rfc7628#section-3.1] + # formatted response. + def initial_client_response + kv_pairs = { + host: host, port: port, mthd: mthd, path: path, post: post, qs: qs, + auth: authorization, # authorization is implemented by subclasses + }.compact + [gs2_header, *kv_pairs.map {|kv| kv.join("=") }, "\1"].join("\1") + end + + # Returns initial_client_response the first time, then "^A". + def process(data) + @last_server_response = data + done? ? "\1" : initial_client_response + ensure + @done = true + end + + # Returns true when the initial client response was sent. + # + # The authentication should not succeed unless this returns true, but it + # does *not* indicate success. + def done?; @done end + + # Value of the HTTP Authorization header + # + # Implemented by subclasses. + def authorization; raise "must be implemented by subclass" end + + end + + # Authenticator for the "+OAUTHBEARER+" SASL mechanism, specified in + # RFC7628[https://tools.ietf.org/html/rfc7628]. Authenticates using OAuth + # 2.0 bearer tokens, as described in + # RFC6750[https://tools.ietf.org/html/rfc6750]. Use via + # Net::IMAP#authenticate. + # + # RFC6750[https://tools.ietf.org/html/rfc6750] requires Transport Layer + # Security (TLS) to secure the protocol interaction between the client and + # the resource server. TLS _MUST_ be used for +OAUTHBEARER+ to protect + # the bearer token. + class OAuthBearerAuthenticator < OAuthAuthenticator + + # An OAuth 2.0 bearer token. See {RFC-6750}[https://www.rfc-editor.org/rfc/rfc6750] + attr_reader :oauth2_token + alias secret oauth2_token + + # :call-seq: + # new(oauth2_token, **options) -> authenticator + # new(authzid, oauth2_token, **options) -> authenticator + # new(oauth2_token:, **options) -> authenticator + # + # Creates an Authenticator for the "+OAUTHBEARER+" SASL mechanism. + # + # Called by Net::IMAP#authenticate and similar methods on other clients. + # + # ==== Parameters + # + # * #oauth2_token — An OAuth2 bearer token + # + # All other keyword parameters are passed to + # {super}[rdoc-ref:OAuthAuthenticator::new] (see OAuthAuthenticator). + # The most common ones are: + # + # * _optional_ #authzid ― Authorization identity to act as or on behalf of. + # + # _optional_ #username — An alias for #authzid. + # + # Note that, unlike some other authenticators, +username+ sets the + # _authorization_ identity and not the _authentication_ identity. The + # authentication identity is established for the client by + # #oauth2_token. + # + # * _optional_ #host — Hostname to which the client connected. + # * _optional_ #port — Service port to which the client connected. + # + # Although only oauth2_token is required by this mechanism, it is worth + # noting that application protocols are allowed to + # require #authzid (or other parameters, such as #host + # _or_ #port) as are specific server implementations. + def initialize(arg1 = nil, arg2 = nil, + oauth2_token: nil, secret: nil, + **args, &blk) + username, oauth2_token_arg = arg2.nil? ? [nil, arg1] : [arg1, arg2] + super(username: username, **args, &blk) + @oauth2_token = oauth2_token || secret || oauth2_token_arg or + raise ArgumentError, "missing oauth2_token" + end + + # :call-seq: + # initial_response? -> true + # + # +OAUTHBEARER+ sends an initial client response. + def initial_response?; true end + + # Value of the HTTP Authorization header + def authorization; "Bearer #{oauth2_token}" end + + end + end + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/plain_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/plain_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/plain_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/plain_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +# Authenticator for the "+PLAIN+" SASL mechanism, specified in +# RFC-4616[https://tools.ietf.org/html/rfc4616]. See Net::IMAP#authenticate. +# +# +PLAIN+ authentication sends the password in cleartext. +# RFC-3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable +# cleartext authentication until after TLS has been negotiated. +# RFC-8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or +# greater be used for all traffic, and deprecate cleartext access ASAP. +PLAIN+ +# can be secured by TLS encryption. +class Net::IMAP::SASL::PlainAuthenticator + + NULL = -"\0".b + private_constant :NULL + + # Authentication identity: the identity that matches the #password. + # + # RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+. + # "Authentication identity" is the generic term used by + # RFC-4422[https://tools.ietf.org/html/rfc4422]. + # RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate + # this to +authcid+. + attr_reader :username + alias authcid username + + # A password or passphrase that matches the #username. + attr_reader :password + alias secret password + + # Authorization identity: an identity to act as or on behalf of. The identity + # form is application protocol specific. If not provided or left blank, the + # server derives an authorization identity from the authentication identity. + # The server is responsible for verifying the client's credentials and + # verifying that the identity it associates with the client's authentication + # identity is allowed to act as (or on behalf of) the authorization identity. + # + # For example, an administrator or superuser might take on another role: + # + # imap.authenticate "PLAIN", "root", passwd, authzid: "user" + # + attr_reader :authzid + + # :call-seq: + # new(username, password, authzid: nil, **) -> authenticator + # new(username:, password:, authzid: nil, **) -> authenticator + # new(authcid:, password:, authzid: nil, **) -> authenticator + # + # Creates an Authenticator for the "+PLAIN+" SASL mechanism. + # + # Called by Net::IMAP#authenticate and similar methods on other clients. + # + # ==== Parameters + # + # * #authcid ― Authentication identity that is associated with #password. + # + # #username ― An alias for #authcid. + # + # * #password ― A password or passphrase associated with the #authcid. + # + # * _optional_ #authzid ― Authorization identity to act as or on behalf of. + # + # When +authzid+ is not set, the server should derive the authorization + # identity from the authentication identity. + # + # Any other keyword parameters are quietly ignored. + def initialize(user = nil, pass = nil, + authcid: nil, secret: nil, + username: nil, password: nil, authzid: nil, **) + username ||= authcid || user or + raise ArgumentError, "missing username (authcid)" + password ||= secret || pass or raise ArgumentError, "missing password" + raise ArgumentError, "username contains NULL" if username.include?(NULL) + raise ArgumentError, "password contains NULL" if password.include?(NULL) + raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL) + @username = username + @password = password + @authzid = authzid + @done = false + end + + # :call-seq: + # initial_response? -> true + # + # +PLAIN+ can send an initial client response. + def initial_response?; true end + + # Responds with the client's credentials. + def process(data) + return "#@authzid\0#@username\0#@password" + ensure + @done = true + end + + # Returns true when the initial client response was sent. + # + # The authentication should not succeed unless this returns true, but it + # does *not* indicate success. + def done?; @done end + +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/protocol_adapters.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/protocol_adapters.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/protocol_adapters.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/protocol_adapters.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Net + class IMAP + module SASL + + module ProtocolAdapters + # This API is experimental, and may change. + module Generic + def command_name; "AUTHENTICATE" end + def service; raise "Implement in subclass or module" end + def host; client.host end + def port; client.port end + def encode_ir(string) string.empty? ? "=" : encode(string) end + def encode(string) [string].pack("m0") end + def decode(string) string.unpack1("m0") end + def cancel_response; "*" end + end + + # See RFC-3501 (IMAP4rev1), RFC-4959 (SASL-IR capability), + # and RFC-9051 (IMAP4rev2). + module IMAP + include Generic + def service; "imap" end + end + + # See RFC-4954 (AUTH capability). + module SMTP + include Generic + def command_name; "AUTH" end + def service; "smtp" end + end + + # See RFC-5034 (SASL capability). + module POP + include Generic + def command_name; "AUTH" end + def service; "pop" end + end + + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/scram_algorithm.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/scram_algorithm.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/scram_algorithm.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/scram_algorithm.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Net + class IMAP + module SASL + + # For method descriptions, + # see {RFC5802 §2.2}[https://www.rfc-editor.org/rfc/rfc5802#section-2.2] + # and {RFC5802 §3}[https://www.rfc-editor.org/rfc/rfc5802#section-3]. + module ScramAlgorithm + def Normalize(str) SASL.saslprep(str) end + + def Hi(str, salt, iterations) + length = digest.digest_length + OpenSSL::KDF.pbkdf2_hmac( + str, + salt: salt, + iterations: iterations, + length: length, + hash: digest, + ) + end + + def H(str) digest.digest str end + + def HMAC(key, data) OpenSSL::HMAC.digest(digest, key, data) end + + def XOR(str1, str2) + str1.unpack("C*") + .zip(str2.unpack("C*")) + .map {|a, b| a ^ b } + .pack("C*") + end + + def auth_message + [ + client_first_message_bare, + server_first_message, + client_final_message_without_proof, + ] + .join(",") + end + + def salted_password + Hi(Normalize(password), salt, iterations) + end + + def client_key; HMAC(salted_password, "Client Key") end + def server_key; HMAC(salted_password, "Server Key") end + def stored_key; H(client_key) end + def client_signature; HMAC(stored_key, auth_message) end + def server_signature; HMAC(server_key, auth_message) end + def client_proof; XOR(client_key, client_signature) end + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/scram_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/scram_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/scram_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/scram_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,287 @@ +# frozen_string_literal: true + +require "openssl" +require "securerandom" + +require_relative "gs2_header" +require_relative "scram_algorithm" + +module Net + class IMAP + module SASL + + # Abstract base class for the "+SCRAM-*+" family of SASL mechanisms, + # defined in RFC5802[https://tools.ietf.org/html/rfc5802]. Use via + # Net::IMAP#authenticate. + # + # Directly supported: + # * +SCRAM-SHA-1+ --- ScramSHA1Authenticator + # * +SCRAM-SHA-256+ --- ScramSHA256Authenticator + # + # New +SCRAM-*+ mechanisms can easily be added for any hash algorithm + # supported by + # OpenSSL::Digest[https://ruby.github.io/openssl/OpenSSL/Digest.html]. + # Subclasses need only set an appropriate +DIGEST_NAME+ constant. + # + # === SCRAM algorithm + # + # See the documentation and method definitions on ScramAlgorithm for an + # overview of the algorithm. The different mechanisms differ only by + # which hash function that is used (or by support for channel binding with + # +-PLUS+). + # + # See also the methods on GS2Header. + # + # ==== Server messages + # + # As server messages are received, they are validated and loaded into + # the various attributes, e.g: #snonce, #salt, #iterations, #verifier, + # #server_error, etc. + # + # Unlike many other SASL mechanisms, the +SCRAM-*+ family supports mutual + # authentication and can return server error data in the server messages. + # If #process raises an Error for the server-final-message, then + # server_error may contain error details. + # + # === TLS Channel binding + # + # The SCRAM-*-PLUS mechanisms and channel binding are not + # supported yet. + # + # === Caching SCRAM secrets + # + # Caching of salted_password, client_key, stored_key, and server_key + # is not supported yet. + # + class ScramAuthenticator + include GS2Header + include ScramAlgorithm + + # :call-seq: + # new(username, password, **options) -> auth_ctx + # new(username:, password:, **options) -> auth_ctx + # new(authcid:, password:, **options) -> auth_ctx + # + # Creates an authenticator for one of the "+SCRAM-*+" SASL mechanisms. + # Each subclass defines #digest to match a specific mechanism. + # + # Called by Net::IMAP#authenticate and similar methods on other clients. + # + # === Parameters + # + # * #authcid ― Identity whose #password is used. + # + # #username - An alias for #authcid. + # * #password ― Password or passphrase associated with this #username. + # * _optional_ #authzid ― Alternate identity to act as or on behalf of. + # * _optional_ #min_iterations - Overrides the default value (4096). + # + # Any other keyword parameters are quietly ignored. + def initialize(username_arg = nil, password_arg = nil, + authcid: nil, username: nil, + authzid: nil, + password: nil, secret: nil, + min_iterations: 4096, # see both RFC5802 and RFC7677 + cnonce: nil, # must only be set in tests + **options) + @username = username || username_arg || authcid or + raise ArgumentError, "missing username (authcid)" + @password = password || secret || password_arg or + raise ArgumentError, "missing password" + @authzid = authzid + + @min_iterations = Integer min_iterations + @min_iterations.positive? or + raise ArgumentError, "min_iterations must be positive" + + @cnonce = cnonce || SecureRandom.base64(32) + end + + # Authentication identity: the identity that matches the #password. + # + # RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+. + # "Authentication identity" is the generic term used by + # RFC-4422[https://tools.ietf.org/html/rfc4422]. + # RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate + # this to +authcid+. + attr_reader :username + alias authcid username + + # A password or passphrase that matches the #username. + attr_reader :password + alias secret password + + # Authorization identity: an identity to act as or on behalf of. The + # identity form is application protocol specific. If not provided or + # left blank, the server derives an authorization identity from the + # authentication identity. For example, an administrator or superuser + # might take on another role: + # + # imap.authenticate "SCRAM-SHA-256", "root", passwd, authzid: "user" + # + # The server is responsible for verifying the client's credentials and + # verifying that the identity it associates with the client's + # authentication identity is allowed to act as (or on behalf of) the + # authorization identity. + attr_reader :authzid + + # The minimal allowed iteration count. Lower #iterations will raise an + # Error. + attr_reader :min_iterations + + # The client nonce, generated by SecureRandom + attr_reader :cnonce + + # The server nonce, which must start with #cnonce + attr_reader :snonce + + # The salt used by the server for this user + attr_reader :salt + + # The iteration count for the selected hash function and user + attr_reader :iterations + + # An error reported by the server during the \SASL exchange. + # + # Does not include errors reported by the protocol, e.g. + # Net::IMAP::NoResponseError. + attr_reader :server_error + + # Returns a new OpenSSL::Digest object, set to the appropriate hash + # function for the chosen mechanism. + # + # The class's +DIGEST_NAME+ constant must be set to the name of an + # algorithm supported by OpenSSL::Digest. + def digest; OpenSSL::Digest.new self.class::DIGEST_NAME end + + # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] + # +client-first-message+. + def initial_client_response + "#{gs2_header}#{client_first_message_bare}" + end + + # responds to the server's challenges + def process(challenge) + case (@state ||= :initial_client_response) + when :initial_client_response + initial_client_response.tap { @state = :server_first_message } + when :server_first_message + recv_server_first_message challenge + final_message_with_proof.tap { @state = :server_final_message } + when :server_final_message + recv_server_final_message challenge + "".tap { @state = :done } + else + raise Error, "server sent after complete, %p" % [challenge] + end + rescue Exception => ex + @state = ex + raise + end + + # Is the authentication exchange complete? + # + # If false, another server continuation is required. + def done?; @state == :done end + + private + + # Need to store this for auth_message + attr_reader :server_first_message + + def format_message(hash) hash.map { _1.join("=") }.join(",") end + + def recv_server_first_message(server_first_message) + @server_first_message = server_first_message + sparams = parse_challenge server_first_message + @snonce = sparams["r"] or + raise Error, "server did not send nonce" + @salt = sparams["s"]&.unpack1("m") or + raise Error, "server did not send salt" + @iterations = sparams["i"]&.then {|i| Integer i } or + raise Error, "server did not send iteration count" + min_iterations <= iterations or + raise Error, "too few iterations: %d" % [iterations] + mext = sparams["m"] and + raise Error, "mandatory extension: %p" % [mext] + snonce.start_with? cnonce or + raise Error, "invalid server nonce" + end + + def recv_server_final_message(server_final_message) + sparams = parse_challenge server_final_message + @server_error = sparams["e"] and + raise Error, "server error: %s" % [server_error] + verifier = sparams["v"].unpack1("m") or + raise Error, "server did not send verifier" + verifier == server_signature or + raise Error, "server verify failed: %p != %p" % [ + server_signature, verifier + ] + end + + # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] + # +client-first-message-bare+. + def client_first_message_bare + @client_first_message_bare ||= + format_message(n: gs2_saslname_encode(SASL.saslprep(username)), + r: cnonce) + end + + # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] + # +client-final-message+. + def final_message_with_proof + proof = [client_proof].pack("m0") + "#{client_final_message_without_proof},p=#{proof}" + end + + # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] + # +client-final-message-without-proof+. + def client_final_message_without_proof + @client_final_message_without_proof ||= + format_message(c: [cbind_input].pack("m0"), # channel-binding + r: snonce) # nonce + end + + # See {RFC5802 §7}[https://www.rfc-editor.org/rfc/rfc5802#section-7] + # +cbind-input+. + # + # >>> + # *TODO:* implement channel binding, appending +cbind-data+ here. + alias cbind_input gs2_header + + # RFC5802 specifies "that the order of attributes in client or server + # messages is fixed, with the exception of extension attributes", but + # this parses it simply as a hash, without respect to order. Note that + # repeated keys (violating the spec) will use the last value. + def parse_challenge(challenge) + challenge.split(/,/).to_h {|pair| pair.split(/=/, 2) } + rescue ArgumentError + raise Error, "unparsable challenge: %p" % [challenge] + end + + end + + # Authenticator for the "+SCRAM-SHA-1+" SASL mechanism, defined in + # RFC5802[https://tools.ietf.org/html/rfc5802]. + # + # Uses the "SHA-1" digest algorithm from OpenSSL::Digest. + # + # See ScramAuthenticator. + class ScramSHA1Authenticator < ScramAuthenticator + DIGEST_NAME = "SHA1" + end + + # Authenticator for the "+SCRAM-SHA-256+" SASL mechanism, defined in + # RFC7677[https://tools.ietf.org/html/rfc7677]. + # + # Uses the "SHA-256" digest algorithm from OpenSSL::Digest. + # + # See ScramAuthenticator. + class ScramSHA256Authenticator < ScramAuthenticator + DIGEST_NAME = "SHA256" + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/stringprep.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/stringprep.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/stringprep.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/stringprep.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Net::IMAP::SASL + + # Alias for Net::IMAP::StringPrep::SASLprep. + SASLprep = Net::IMAP::StringPrep::SASLprep + StringPrep = Net::IMAP::StringPrep # :nodoc: + BidiStringError = Net::IMAP::StringPrep::BidiStringError # :nodoc: + ProhibitedCodepoint = Net::IMAP::StringPrep::ProhibitedCodepoint # :nodoc: + StringPrepError = Net::IMAP::StringPrep::StringPrepError # :nodoc: + +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/xoauth2_authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/xoauth2_authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/xoauth2_authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl/xoauth2_authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +# Authenticator for the "+XOAUTH2+" SASL mechanism. This mechanism was +# originally created for GMail and widely adopted by hosted email providers. +# +XOAUTH2+ has been documented by +# Google[https://developers.google.com/gmail/imap/xoauth2-protocol] and +# Microsoft[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth]. +# +# This mechanism requires an OAuth2 access token which has been authorized +# with the appropriate OAuth2 scopes to access the user's services. Most of +# these scopes are not standardized---consult each service provider's +# documentation for their scopes. +# +# Although this mechanism was never standardized and has been obsoleted by +# "+OAUTHBEARER+", it is still very widely supported. +# +# See Net::IMAP::SASL::OAuthBearerAuthenticator. +class Net::IMAP::SASL::XOAuth2Authenticator + + # It is unclear from {Google's original XOAUTH2 + # documentation}[https://developers.google.com/gmail/imap/xoauth2-protocol], + # whether "User" refers to the authentication identity (+authcid+) or the + # authorization identity (+authzid+). The authentication identity is + # established for the client by the OAuth token, so it seems that +username+ + # must be the authorization identity. + # + # {Microsoft's documentation for shared + # mailboxes}[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#sasl-xoauth2-authentication-for-shared-mailboxes-in-office-365] + # _clearly_ indicates that the Office 365 server interprets it as the + # authorization identity. + # + # Although they _should_ validate that the token has been authorized to access + # the service for +username+, _some_ servers appear to ignore this field, + # relying only the identity and scope authorized by the token. + attr_reader :username + + # Note that, unlike most other authenticators, #username is an alias for the + # authorization identity and not the authentication identity. The + # authenticated identity is established for the client by the #oauth2_token. + alias authzid username + + # An OAuth2 access token which has been authorized with the appropriate OAuth2 + # scopes to use the service for #username. + attr_reader :oauth2_token + alias secret oauth2_token + + # :call-seq: + # new(username, oauth2_token, **) -> authenticator + # new(username:, oauth2_token:, **) -> authenticator + # new(authzid:, oauth2_token:, **) -> authenticator + # + # Creates an Authenticator for the "+XOAUTH2+" SASL mechanism, as specified by + # Google[https://developers.google.com/gmail/imap/xoauth2-protocol], + # Microsoft[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth] + # and Yahoo[https://senders.yahooinc.com/developer/documentation]. + # + # === Properties + # + # * #username --- the username for the account being accessed. + # + # #authzid --- an alias for #username. + # + # Note that, unlike some other authenticators, +username+ sets the + # _authorization_ identity and not the _authentication_ identity. The + # authenticated identity is established for the client with the OAuth token. + # + # * #oauth2_token --- An OAuth2.0 access token which is authorized to access + # the service for #username. + # + # Any other keyword parameters are quietly ignored. + def initialize(user = nil, token = nil, username: nil, oauth2_token: nil, + authzid: nil, secret: nil, **) + @username = authzid || username || user or + raise ArgumentError, "missing username (authzid)" + @oauth2_token = oauth2_token || secret || token or + raise ArgumentError, "missing oauth2_token" + @done = false + end + + # :call-seq: + # initial_response? -> true + # + # +XOAUTH2+ can send an initial client response. + def initial_response?; true end + + # Returns the XOAUTH2 formatted response, which combines the +username+ + # with the +oauth2_token+. + def process(_data) + build_oauth2_string(@username, @oauth2_token) + ensure + @done = true + end + + # Returns true when the initial client response was sent. + # + # The authentication should not succeed unless this returns true, but it + # does *not* indicate success. + def done?; @done end + + private + + def build_oauth2_string(username, oauth2_token) + format("user=%s\1auth=Bearer %s\1\1", username, oauth2_token) + end + +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,179 @@ +# frozen_string_literal: true + +module Net + class IMAP + + # Pluggable authentication mechanisms for protocols which support SASL + # (Simple Authentication and Security Layer), such as IMAP4, SMTP, LDAP, and + # XMPP. {RFC-4422}[https://tools.ietf.org/html/rfc4422] specifies the + # common \SASL framework: + # >>> + # SASL is conceptually a framework that provides an abstraction layer + # between protocols and mechanisms as illustrated in the following + # diagram. + # + # SMTP LDAP XMPP Other protocols ... + # \ | | / + # \ | | / + # SASL abstraction layer + # / | | \ + # / | | \ + # EXTERNAL GSSAPI PLAIN Other mechanisms ... + # + # Net::IMAP uses SASL via the Net::IMAP#authenticate method. + # + # == Mechanisms + # + # Each mechanism has different properties and requirements. Please consult + # the documentation for the specific mechanisms you are using: + # + # +ANONYMOUS+:: + # See AnonymousAuthenticator. + # + # Allows the user to gain access to public services or resources without + # authenticating or disclosing an identity. + # + # +EXTERNAL+:: + # See ExternalAuthenticator. + # + # Authenticates using already established credentials, such as a TLS + # certificate or IPsec. + # + # +OAUTHBEARER+:: + # See OAuthBearerAuthenticator. + # + # Login using an OAuth2 Bearer token. This is the standard mechanism + # for using OAuth2 with \SASL, but it is not yet deployed as widely as + # +XOAUTH2+. + # + # +PLAIN+:: + # See PlainAuthenticator. + # + # Login using clear-text username and password. + # + # +SCRAM-SHA-1+:: + # +SCRAM-SHA-256+:: + # See ScramAuthenticator. + # + # Login by username and password. The password is not sent to the + # server but is used in a salted challenge/response exchange. + # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by + # Net::IMAP::SASL. New authenticators can easily be added for any other + # SCRAM-* mechanism if the digest algorithm is supported by + # OpenSSL::Digest. + # + # +XOAUTH2+:: + # See XOAuth2Authenticator. + # + # Login using a username and an OAuth2 access token. Non-standard and + # obsoleted by +OAUTHBEARER+, but widely supported. + # + # See the {SASL mechanism + # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # for a list of all SASL mechanisms and their specifications. To register + # new authenticators, see Authenticators. + # + # === Deprecated mechanisms + # + # Obsolete mechanisms should be avoided, but are still available for + # backwards compatibility. + # + # >>> + # For +DIGEST-MD5+ see DigestMD5Authenticator. + # + # For +LOGIN+, see LoginAuthenticator. + # + # For +CRAM-MD5+, see CramMD5Authenticator. + # + # Using a deprecated mechanism will print a warning. + # + module SASL + # Exception class for any client error detected during the authentication + # exchange. + # + # When the _server_ reports an authentication failure, it will respond + # with a protocol specific error instead, e.g: +BAD+ or +NO+ in IMAP. + # + # When the client encounters any error, it *must* consider the + # authentication exchange to be unsuccessful and it might need to drop the + # connection. For example, if the server reports that the authentication + # exchange was successful or the protocol does not allow additional + # authentication attempts. + Error = Class.new(StandardError) + + # Indicates an authentication exchange that will be or has been canceled + # by the client, not due to any error or failure during processing. + AuthenticationCanceled = Class.new(Error) + + # Indicates an error when processing a server challenge, e.g: an invalid + # or unparsable challenge. An underlying exception may be available as + # the exception's #cause. + AuthenticationError = Class.new(Error) + + # Indicates that authentication cannot proceed because one of the server's + # messages has not passed integrity checks. + AuthenticationFailed = Class.new(Error) + + # Indicates that authentication cannot proceed because one of the server's + # ended authentication prematurely. + class AuthenticationIncomplete < AuthenticationFailed + # The success response from the server + attr_reader :response + + def initialize(response, message = "authentication ended prematurely") + super(message) + @response = response + end + end + + # autoloading to avoid loading all of the regexps when they aren't used. + sasl_stringprep_rb = File.expand_path("sasl/stringprep", __dir__) + autoload :StringPrep, sasl_stringprep_rb + autoload :SASLprep, sasl_stringprep_rb + autoload :StringPrepError, sasl_stringprep_rb + autoload :ProhibitedCodepoint, sasl_stringprep_rb + autoload :BidiStringError, sasl_stringprep_rb + + sasl_dir = File.expand_path("sasl", __dir__) + autoload :AuthenticationExchange, "#{sasl_dir}/authentication_exchange" + autoload :ClientAdapter, "#{sasl_dir}/client_adapter" + autoload :ProtocolAdapters, "#{sasl_dir}/protocol_adapters" + + autoload :Authenticators, "#{sasl_dir}/authenticators" + autoload :GS2Header, "#{sasl_dir}/gs2_header" + autoload :ScramAlgorithm, "#{sasl_dir}/scram_algorithm" + + autoload :AnonymousAuthenticator, "#{sasl_dir}/anonymous_authenticator" + autoload :ExternalAuthenticator, "#{sasl_dir}/external_authenticator" + autoload :OAuthBearerAuthenticator, "#{sasl_dir}/oauthbearer_authenticator" + autoload :PlainAuthenticator, "#{sasl_dir}/plain_authenticator" + autoload :ScramAuthenticator, "#{sasl_dir}/scram_authenticator" + autoload :ScramSHA1Authenticator, "#{sasl_dir}/scram_authenticator" + autoload :ScramSHA256Authenticator, "#{sasl_dir}/scram_authenticator" + autoload :XOAuth2Authenticator, "#{sasl_dir}/xoauth2_authenticator" + + autoload :CramMD5Authenticator, "#{sasl_dir}/cram_md5_authenticator" + autoload :DigestMD5Authenticator, "#{sasl_dir}/digest_md5_authenticator" + autoload :LoginAuthenticator, "#{sasl_dir}/login_authenticator" + + # Returns the default global SASL::Authenticators instance. + def self.authenticators; @authenticators ||= Authenticators.new end + + # Delegates to registry.new See Authenticators#new. + def self.authenticator(*args, registry: authenticators, **kwargs, &block) + registry.new(*args, **kwargs, &block) + end + + # Delegates to ::authenticators. See Authenticators#add_authenticator. + def self.add_authenticator(...) authenticators.add_authenticator(...) end + + module_function + + # See Net::IMAP::StringPrep::SASLprep#saslprep. + def saslprep(string, **opts) + Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts) + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl_adapter.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl_adapter.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl_adapter.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sasl_adapter.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Net + class IMAP + + # Experimental + class SASLAdapter < SASL::ClientAdapter + include SASL::ProtocolAdapters::IMAP + + RESPONSE_ERRORS = [NoResponseError, BadResponseError, ByeResponseError] + .freeze + + def response_errors; RESPONSE_ERRORS end + def sasl_ir_capable?; client.capable?("SASL-IR") end + def auth_capable?(mechanism); client.auth_capable?(mechanism) end + def drop_connection; client.logout! end + def drop_connection!; client.disconnect end + end + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/search_result.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/search_result.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/search_result.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/search_result.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +module Net + class IMAP + + # An array of sequence numbers returned by Net::IMAP#search, or unique + # identifiers returned by Net::IMAP#uid_search. + # + # For backward compatibility, SearchResult inherits from Array. + class SearchResult < Array + + # Returns a frozen SearchResult populated with the given +seq_nums+. + # + # Net::IMAP::SearchResult[1, 3, 5, modseq: 9] + # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9] + def self.[](*seq_nums, modseq: nil) + new(seq_nums, modseq: modseq) + end + + # A modification sequence number, as described by the +CONDSTORE+ + # extension in {[RFC7162 + # §3.1.6]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1.6]. + attr_reader :modseq + + # Returns a frozen SearchResult populated with the given +seq_nums+. + # + # Net::IMAP::SearchResult.new([1, 3, 5], modseq: 9) + # # => Net::IMAP::SearchResult[1, 3, 5, modseq: 9] + def initialize(seq_nums, modseq: nil) + super(seq_nums.to_ary.map { Integer _1 }) + @modseq = Integer modseq if modseq + freeze + end + + # Returns a frozen copy of +other+. + def initialize_copy(other); super; freeze end + + # Returns whether +other+ is a SearchResult with the same values and the + # same #modseq. The order of numbers is irrelevant. + # + # Net::IMAP::SearchResult[123, 456, modseq: 789] == + # Net::IMAP::SearchResult[123, 456, modseq: 789] + # # => true + # Net::IMAP::SearchResult[123, 456, modseq: 789] == + # Net::IMAP::SearchResult[456, 123, modseq: 789] + # # => true + # + # Net::IMAP::SearchResult[123, 456, modseq: 789] == + # Net::IMAP::SearchResult[987, 654, modseq: 789] + # # => false + # Net::IMAP::SearchResult[123, 456, modseq: 789] == + # Net::IMAP::SearchResult[1, 2, 3, modseq: 9999] + # # => false + # + # SearchResult can be compared directly with Array, if #modseq is nil and + # the array is sorted. + # + # Net::IMAP::SearchResult[9, 8, 6, 4, 1] == [1, 4, 6, 8, 9] # => true + # Net::IMAP::SearchResult[3, 5, 7, modseq: 99] == [3, 5, 7] # => false + # + # Note that Array#== does require matching order and ignores #modseq. + # + # [9, 8, 6, 4, 1] == Net::IMAP::SearchResult[1, 4, 6, 8, 9] # => false + # [3, 5, 7] == Net::IMAP::SearchResult[3, 5, 7, modseq: 99] # => true + # + def ==(other) + (modseq ? + other.is_a?(self.class) && modseq == other.modseq : + other.is_a?(Array)) && + size == other.size && + sort == other.sort + end + + # Hash equality. Unlike #==, order will be taken into account. + def hash + return super if modseq.nil? + [super, self.class, modseq].hash + end + + # Hash equality. Unlike #==, order will be taken into account. + def eql?(other) + return super if modseq.nil? + self.class == other.class && hash == other.hash + end + + # Returns a string that represents the SearchResult. + # + # Net::IMAP::SearchResult[123, 456, 789].inspect + # # => "[123, 456, 789]" + # + # Net::IMAP::SearchResult[543, 210, 678, modseq: 2048].inspect + # # => "Net::IMAP::SearchResult[543, 210, 678, modseq: 2048]" + # + def inspect + return super if modseq.nil? + "%s[%s, modseq: %p]" % [self.class, join(", "), modseq] + end + + # Returns a string that follows the formal \IMAP syntax. + # + # data = Net::IMAP::SearchResult[2, 8, 32, 128, 256, 512] + # data.to_s # => "* SEARCH 2 8 32 128 256 512" + # data.to_s("SEARCH") # => "* SEARCH 2 8 32 128 256 512" + # data.to_s("SORT") # => "* SORT 2 8 32 128 256 512" + # data.to_s(nil) # => "2 8 32 128 256 512" + # + # data = Net::IMAP::SearchResult[1, 3, 16, 1024, modseq: 2048].to_s + # data.to_s # => "* SEARCH 1 3 16 1024 (MODSEQ 2048)" + # data.to_s("SORT") # => "* SORT 1 3 16 1024 (MODSEQ 2048)" + # data.to_s # => "1 3 16 1024 (MODSEQ 2048)" + # + def to_s(type = "SEARCH") + str = +"" + str << "* %s " % [type.to_str] unless type.nil? + str << join(" ") + str << " (MODSEQ %d)" % [modseq] if modseq + -str + end + + # Converts the SearchResult into a SequenceSet. + # + # Net::IMAP::SearchResult[9, 1, 2, 4, 10, 12, 3, modseq: 123_456] + # .to_sequence_set + # # => Net::IMAP::SequenceSet["1:4,9:10,12"] + def to_sequence_set; SequenceSet[*self] end + + def pretty_print(pp) + return super if modseq.nil? + pp.text self.class.name + "[" + pp.group_sub do + pp.nest(2) do + pp.breakable "" + each do |num| + pp.pp num + pp.text "," + pp.fill_breakable + end + pp.breakable "" + pp.text "modseq: " + pp.pp modseq + end + pp.breakable "" + pp.text "]" + end + end + + end + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sequence_set.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sequence_set.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sequence_set.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/sequence_set.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,1414 @@ +# frozen_string_literal: true + +module Net + class IMAP + + ## + # An \IMAP sequence set is a set of message sequence numbers or unique + # identifier numbers ("UIDs"). It contains numbers and ranges of numbers. + # The numbers are all non-zero unsigned 32-bit integers and one special + # value ("*") that represents the largest value in the mailbox. + # + # Certain types of \IMAP responses will contain a SequenceSet, for example + # the data for a "MODIFIED" ResponseCode. Some \IMAP commands may + # receive a SequenceSet as an argument, for example IMAP#search, IMAP#fetch, + # and IMAP#store. + # + # == EXPERIMENTAL API + # + # SequenceSet is currently experimental. Only two methods, ::[] and + # #valid_string, are considered stable. Although the API isn't expected to + # change much, any other methods may be removed or changed without + # deprecation. + # + # == Creating sequence sets + # + # SequenceSet.new with no arguments creates an empty sequence set. Note + # that an empty sequence set is invalid in the \IMAP grammar. + # + # set = Net::IMAP::SequenceSet.new + # set.empty? #=> true + # set.valid? #=> false + # set.valid_string #!> raises DataFormatError + # set << 1..10 + # set.empty? #=> false + # set.valid? #=> true + # set.valid_string #=> "1:10" + # + # SequenceSet.new may receive a single optional argument: a non-zero 32 bit + # unsigned integer, a range, a sequence-set formatted string, + # another sequence set, or an enumerable containing any of these. + # + # set = Net::IMAP::SequenceSet.new(1) + # set.valid_string #=> "1" + # set = Net::IMAP::SequenceSet.new(1..100) + # set.valid_string #=> "1:100" + # set = Net::IMAP::SequenceSet.new(1...100) + # set.valid_string #=> "1:99" + # set = Net::IMAP::SequenceSet.new([1, 2, 5..]) + # set.valid_string #=> "1:2,5:*" + # set = Net::IMAP::SequenceSet.new("1,2,3:7,5,6:10,2048,1024") + # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024" + # set = Net::IMAP::SequenceSet.new(1, 2, 3..7, 5, 6..10, 2048, 1024) + # set.valid_string #=> "1:10,55,1024:2048" + # + # Use ::[] with one or more arguments to create a frozen SequenceSet. An + # invalid (empty) set cannot be created with ::[]. + # + # set = Net::IMAP::SequenceSet["1,2,3:7,5,6:10,2048,1024"] + # set.valid_string #=> "1,2,3:7,5,6:10,2048,1024" + # set = Net::IMAP::SequenceSet[1, 2, [3..7, 5], 6..10, 2048, 1024] + # set.valid_string #=> "1:10,55,1024:2048" + # + # == Normalized form + # + # When a sequence set is created with a single String value, that #string + # representation is preserved. SequenceSet's internal representation + # implicitly sorts all entries, de-duplicates numbers, and coalesces + # adjacent or overlapping ranges. Most enumeration methods and offset-based + # methods use this normalized representation. Most modification methods + # will convert #string to its normalized form. + # + # In some cases the order of the string representation is significant, such + # as the +ESORT+, CONTEXT=SORT, and +UIDPLUS+ extensions. Use + # #entries or #each_entry to enumerate the set in its original order. To + # preserve #string order while modifying a set, use #append, #string=, or + # #replace. + # + # == Using * + # + # \IMAP sequence sets may contain a special value "*", which + # represents the largest number in use. From +seq-number+ in + # {RFC9051 §9}[https://www.rfc-editor.org/rfc/rfc9051.html#section-9-5]: + # >>> + # In the case of message sequence numbers, it is the number of messages + # in a non-empty mailbox. In the case of unique identifiers, it is the + # unique identifier of the last message in the mailbox or, if the + # mailbox is empty, the mailbox's current UIDNEXT value. + # + # When creating a SequenceSet, * may be input as -1, + # "*", :*, an endless range, or a range ending in + # -1. When converting to #elements, #ranges, or #numbers, it will + # output as either :* or an endless range. For example: + # + # Net::IMAP::SequenceSet["1,3,*"].to_a #=> [1, 3, :*] + # Net::IMAP::SequenceSet["1,234:*"].to_a #=> [1, 234..] + # Net::IMAP::SequenceSet[1234..-1].to_a #=> [1234..] + # Net::IMAP::SequenceSet[1234..].to_a #=> [1234..] + # + # Net::IMAP::SequenceSet[1234..].to_s #=> "1234:*" + # Net::IMAP::SequenceSet[1234..-1].to_s #=> "1234:*" + # + # Use #limit to convert "*" to a maximum value. When a range + # includes "*", the maximum value will always be matched: + # + # Net::IMAP::SequenceSet["9999:*"].limit(max: 25) + # #=> Net::IMAP::SequenceSet["25"] + # + # === Surprising * behavior + # + # When a set includes *, some methods may have surprising behavior. + # + # For example, #complement treats * as its own number. This way, + # the #intersection of a set and its #complement will always be empty. + # This is not how an \IMAP server interprets the set: it will convert + # * to either the number of messages in the mailbox or +UIDNEXT+, + # as appropriate. And there _will_ be overlap between a set and its + # complement after #limit is applied to each: + # + # ~Net::IMAP::SequenceSet["*"] == Net::IMAP::SequenceSet[1..(2**32-1)] + # ~Net::IMAP::SequenceSet[1..5] == Net::IMAP::SequenceSet["6:*"] + # + # set = Net::IMAP::SequenceSet[1..5] + # (set & ~set).empty? => true + # + # (set.limit(max: 4) & (~set).limit(max: 4)).to_a => [4] + # + # When counting the number of numbers in a set, * will be counted + # _except_ when UINT32_MAX is also in the set: + # UINT32_MAX = 2**32 - 1 + # Net::IMAP::SequenceSet["*"].count => 1 + # Net::IMAP::SequenceSet[1..UINT32_MAX - 1, :*].count => UINT32_MAX + # + # Net::IMAP::SequenceSet["1:*"].count => UINT32_MAX + # Net::IMAP::SequenceSet[UINT32_MAX, :*].count => 1 + # Net::IMAP::SequenceSet[UINT32_MAX..].count => 1 + # + # == What's here? + # + # SequenceSet provides methods for: + # * {Creating a SequenceSet}[rdoc-ref:SequenceSet@Methods+for+Creating+a+SequenceSet] + # * {Comparing}[rdoc-ref:SequenceSet@Methods+for+Comparing] + # * {Querying}[rdoc-ref:SequenceSet@Methods+for+Querying] + # * {Iterating}[rdoc-ref:SequenceSet@Methods+for+Iterating] + # * {Set Operations}[rdoc-ref:SequenceSet@Methods+for+Set+Operations] + # * {Assigning}[rdoc-ref:SequenceSet@Methods+for+Assigning] + # * {Deleting}[rdoc-ref:SequenceSet@Methods+for+Deleting] + # * {IMAP String Formatting}[rdoc-ref:SequenceSet@Methods+for+IMAP+String+Formatting] + # + # === Methods for Creating a \SequenceSet + # * ::[]: Creates a validated frozen sequence set from one or more inputs. + # * ::new: Creates a new mutable sequence set, which may be empty (invalid). + # * ::try_convert: Calls +to_sequence_set+ on an object and verifies that + # the result is a SequenceSet. + # * ::empty: Returns a frozen empty (invalid) SequenceSet. + # * ::full: Returns a frozen SequenceSet containing every possible number. + # + # === Methods for Comparing + # + # Comparison to another \SequenceSet: + # - #==: Returns whether a given set contains the same numbers as +self+. + # - #eql?: Returns whether a given set uses the same #string as +self+. + # + # Comparison to objects which are convertible to \SequenceSet: + # - #===: + # Returns whether a given object is fully contained within +self+, or + # +nil+ if the object cannot be converted to a compatible type. + # - #cover? (aliased as #===): + # Returns whether a given object is fully contained within +self+. + # - #intersect? (aliased as #overlap?): + # Returns whether +self+ and a given object have any common elements. + # - #disjoint?: + # Returns whether +self+ and a given object have no common elements. + # + # === Methods for Querying + # These methods do not modify +self+. + # + # Set membership: + # - #include? (aliased as #member?): + # Returns whether a given object (nz-number, range, or *) is + # contained by the set. + # - #include_star?: Returns whether the set contains *. + # + # Minimum and maximum value elements: + # - #min: Returns the minimum number in the set. + # - #max: Returns the maximum number in the set. + # - #minmax: Returns the minimum and maximum numbers in the set. + # + # Accessing value by offset: + # - #[] (aliased as #slice): Returns the number or consecutive subset at a + # given offset or range of offsets. + # - #at: Returns the number at a given offset. + # - #find_index: Returns the given number's offset in the set + # + # Set cardinality: + # - #count (aliased as #size): Returns the count of numbers in the set. + # - #empty?: Returns whether the set has no members. \IMAP syntax does not + # allow empty sequence sets. + # - #valid?: Returns whether the set has any members. + # - #full?: Returns whether the set contains every possible value, including + # *. + # + # === Methods for Iterating + # + # - #each_element: Yields each number and range in the set, sorted and + # coalesced, and returns +self+. + # - #elements (aliased as #to_a): Returns an Array of every number and range + # in the set, sorted and coalesced. + # - #each_entry: Yields each number and range in the set, unsorted and + # without deduplicating numbers or coalescing ranges, and returns +self+. + # - #entries: Returns an Array of every number and range in the set, + # unsorted and without deduplicating numbers or coalescing ranges. + # - #each_range: + # Yields each element in the set as a Range and returns +self+. + # - #ranges: Returns an Array of every element in the set, converting + # numbers into ranges of a single value. + # - #each_number: Yields each number in the set and returns +self+. + # - #numbers: Returns an Array with every number in the set, expanding + # ranges into all of their contained numbers. + # - #to_set: Returns a Set containing all of the #numbers in the set. + # + # === Methods for \Set Operations + # These methods do not modify +self+. + # + # - #| (aliased as #union and #+): Returns a new set combining all members + # from +self+ with all members from the other object. + # - #& (aliased as #intersection): Returns a new set containing all members + # common to +self+ and the other object. + # - #- (aliased as #difference): Returns a copy of +self+ with all members + # in the other object removed. + # - #^ (aliased as #xor): Returns a new set containing all members from + # +self+ and the other object except those common to both. + # - #~ (aliased as #complement): Returns a new set containing all members + # that are not in +self+ + # - #limit: Returns a copy of +self+ which has replaced * with a + # given maximum value and removed all members over that maximum. + # + # === Methods for Assigning + # These methods add or replace elements in +self+. + # + # - #add (aliased as #<<): Adds a given object to the set; returns +self+. + # - #add?: If the given object is not an element in the set, adds it and + # returns +self+; otherwise, returns +nil+. + # - #merge: Merges multiple elements into the set; returns +self+. + # - #append: Adds a given object to the set, appending it to the existing + # string, and returns +self+. + # - #string=: Assigns a new #string value and replaces #elements to match. + # - #replace: Replaces the contents of the set with the contents + # of a given object. + # - #complement!: Replaces the contents of the set with its own #complement. + # + # === Methods for Deleting + # These methods remove elements from +self+. + # + # - #clear: Removes all elements in the set; returns +self+. + # - #delete: Removes a given object from the set; returns +self+. + # - #delete?: If the given object is an element in the set, removes it and + # returns it; otherwise, returns +nil+. + # - #delete_at: Removes the number at a given offset. + # - #slice!: Removes the number or consecutive numbers at a given offset or + # range of offsets. + # - #subtract: Removes each given object from the set; returns +self+. + # - #limit!: Replaces * with a given maximum value and removes all + # members over that maximum; returns +self+. + # + # === Methods for \IMAP String Formatting + # + # - #to_s: Returns the +sequence-set+ string, or an empty string when the + # set is empty. + # - #string: Returns the +sequence-set+ string, or nil when empty. + # - #valid_string: Returns the +sequence-set+ string, or raises + # DataFormatError when the set is empty. + # - #normalized_string: Returns a sequence-set string with its + # elements sorted and coalesced, or nil when the set is empty. + # - #normalize: Returns a new set with this set's normalized +sequence-set+ + # representation. + # - #normalize!: Updates #string to its normalized +sequence-set+ + # representation and returns +self+. + # + class SequenceSet + # The largest possible non-zero unsigned 32-bit integer + UINT32_MAX = 2**32 - 1 + + # represents "*" internally, to simplify sorting (etc) + STAR_INT = UINT32_MAX + 1 + private_constant :STAR_INT + + # valid inputs for "*" + STARS = [:*, ?*, -1].freeze + private_constant :STAR_INT, :STARS + + COERCIBLE = ->{ _1.respond_to? :to_sequence_set } + ENUMABLE = ->{ _1.respond_to?(:each) && _1.respond_to?(:empty?) } + private_constant :COERCIBLE, :ENUMABLE + + class << self + + # :call-seq: + # SequenceSet[*values] -> valid frozen sequence set + # + # Returns a frozen SequenceSet, constructed from +values+. + # + # An empty SequenceSet is invalid and will raise a DataFormatError. + # + # Use ::new to create a mutable or empty SequenceSet. + def [](first, *rest) + if rest.empty? + if first.is_a?(SequenceSet) && set.frozen? && set.valid? + first + else + new(first).validate.freeze + end + else + new(first).merge(*rest).validate.freeze + end + end + + # :call-seq: + # SequenceSet.try_convert(obj) -> sequence set or nil + # + # If +obj+ is a SequenceSet, returns +obj+. If +obj+ responds_to + # +to_sequence_set+, calls +obj.to_sequence_set+ and returns the result. + # Otherwise returns +nil+. + # + # If +obj.to_sequence_set+ doesn't return a SequenceSet, an exception is + # raised. + def try_convert(obj) + return obj if obj.is_a?(SequenceSet) + return nil unless respond_to?(:to_sequence_set) + obj = obj.to_sequence_set + return obj if obj.is_a?(SequenceSet) + raise DataFormatError, "invalid object returned from to_sequence_set" + end + + # Returns a frozen empty set singleton. Note that valid \IMAP sequence + # sets cannot be empty, so this set is _invalid_. + def empty; EMPTY end + + # Returns a frozen full set singleton: "1:*" + def full; FULL end + + end + + # Create a new SequenceSet object from +input+, which may be another + # SequenceSet, an IMAP formatted +sequence-set+ string, a number, a + # range, :*, or an enumerable of these. + # + # Use ::[] to create a frozen (non-empty) SequenceSet. + def initialize(input = nil) input ? replace(input) : clear end + + # Removes all elements and returns self. + def clear; @tuples, @string = [], nil; self end + + # Replace the contents of the set with the contents of +other+ and returns + # +self+. + # + # +other+ may be another SequenceSet, or it may be an IMAP +sequence-set+ + # string, a number, a range, *, or an enumerable of these. + def replace(other) + case other + when SequenceSet then initialize_dup(other) + when String then self.string = other + else clear; merge other + end + self + end + + # Returns the \IMAP +sequence-set+ string representation, or raises a + # DataFormatError when the set is empty. + # + # Use #string to return +nil+ or #to_s to return an empty string without + # error. + # + # Related: #string, #normalized_string, #to_s + def valid_string + raise DataFormatError, "empty sequence-set" if empty? + string + end + + # Returns the \IMAP +sequence-set+ string representation, or +nil+ when + # the set is empty. Note that an empty set is invalid in the \IMAP + # syntax. + # + # Use #valid_string to raise an exception when the set is empty, or #to_s + # to return an empty string. + # + # If the set was created from a single string, it is not normalized. If + # the set is updated the string will be normalized. + # + # Related: #valid_string, #normalized_string, #to_s + def string; @string ||= normalized_string if valid? end + + # Assigns a new string to #string and resets #elements to match. It + # cannot be set to an empty string—assign +nil+ or use #clear instead. + # The string is validated but not normalized. + # + # Use #add or #merge to add a string to an existing set. + # + # Related: #replace, #clear + def string=(str) + if str.nil? + clear + else + str = String.try_convert(str) or raise ArgumentError, "not a string" + tuples = str_to_tuples str + @tuples, @string = [], -str + tuples_add tuples + end + end + + # Returns the \IMAP +sequence-set+ string representation, or an empty + # string when the set is empty. Note that an empty set is invalid in the + # \IMAP syntax. + # + # Related: #valid_string, #normalized_string, #to_s + def to_s; string || "" end + + # Freezes and returns the set. A frozen SequenceSet is Ractor-safe. + def freeze + return self if frozen? + string + @tuples.each(&:freeze).freeze + super + end + + # :call-seq: self == other -> true or false + # + # Returns true when the other SequenceSet represents the same message + # identifiers. Encoding difference—such as order, overlaps, or + # duplicates—are ignored. + # + # Net::IMAP::SequenceSet["1:3"] == Net::IMAP::SequenceSet["1:3"] + # #=> true + # Net::IMAP::SequenceSet["1,2,3"] == Net::IMAP::SequenceSet["1:3"] + # #=> true + # Net::IMAP::SequenceSet["1,3"] == Net::IMAP::SequenceSet["3,1"] + # #=> true + # Net::IMAP::SequenceSet["9,1:*"] == Net::IMAP::SequenceSet["1:*"] + # #=> true + # + # Related: #eql?, #normalize + def ==(other) + self.class == other.class && + (to_s == other.to_s || tuples == other.tuples) + end + + # :call-seq: eql?(other) -> true or false + # + # Hash equality requires the same encoded #string representation. + # + # Net::IMAP::SequenceSet["1:3"] .eql? Net::IMAP::SequenceSet["1:3"] + # #=> true + # Net::IMAP::SequenceSet["1,2,3"].eql? Net::IMAP::SequenceSet["1:3"] + # #=> false + # Net::IMAP::SequenceSet["1,3"] .eql? Net::IMAP::SequenceSet["3,1"] + # #=> false + # Net::IMAP::SequenceSet["9,1:*"].eql? Net::IMAP::SequenceSet["1:*"] + # #=> false + # + # Related: #==, #normalize + def eql?(other) self.class == other.class && string == other.string end + + # See #eql? + def hash; [self.class, string].hash end + + # :call-seq: self === other -> true | false | nil + # + # Returns whether +other+ is contained within the set. Returns +nil+ if a + # StandardError is raised while converting +other+ to a comparable type. + # + # Related: #cover?, #include?, #include_star? + def ===(other) + cover?(other) + rescue + nil + end + + # :call-seq: cover?(other) -> true | false | nil + # + # Returns whether +other+ is contained within the set. +other+ may be any + # object that would be accepted by ::new. + # + # Related: #===, #include?, #include_star? + def cover?(other) input_to_tuples(other).none? { !include_tuple?(_1) } end + + # Returns +true+ when a given number or range is in +self+, and +false+ + # otherwise. Returns +false+ unless +number+ is an Integer, Range, or + # *. + # + # set = Net::IMAP::SequenceSet["5:10,100,111:115"] + # set.include? 1 #=> false + # set.include? 5..10 #=> true + # set.include? 11..20 #=> false + # set.include? 100 #=> true + # set.include? 6 #=> true, covered by "5:10" + # set.include? 4..9 #=> true, covered by "5:10" + # set.include? "4:9" #=> true, strings are parsed + # set.include? 4..9 #=> false, intersection is not sufficient + # set.include? "*" #=> false, use #limit to re-interpret "*" + # set.include? -1 #=> false, -1 is interpreted as "*" + # + # set = Net::IMAP::SequenceSet["5:10,100,111:*"] + # set.include? :* #=> true + # set.include? "*" #=> true + # set.include? -1 #=> true + # set.include? 200.. #=> true + # set.include? 100.. #=> false + # + # Related: #include_star?, #cover?, #=== + def include?(element) include_tuple? input_to_tuple element end + + alias member? include? + + # Returns +true+ when the set contains *. + def include_star?; @tuples.last&.last == STAR_INT end + + # Returns +true+ if the set and a given object have any common elements, + # +false+ otherwise. + # + # Net::IMAP::SequenceSet["5:10"].intersect? "7,9,11" #=> true + # Net::IMAP::SequenceSet["5:10"].intersect? "11:33" #=> false + # + # Related: #intersection, #disjoint? + def intersect?(other) + valid? && input_to_tuples(other).any? { intersect_tuple? _1 } + end + alias overlap? intersect? + + # Returns +true+ if the set and a given object have no common elements, + # +false+ otherwise. + # + # Net::IMAP::SequenceSet["5:10"].disjoint? "7,9,11" #=> false + # Net::IMAP::SequenceSet["5:10"].disjoint? "11:33" #=> true + # + # Related: #intersection, #intersect? + def disjoint?(other) + empty? || input_to_tuples(other).none? { intersect_tuple? _1 } + end + + # :call-seq: max(star: :*) => integer or star or nil + # + # Returns the maximum value in +self+, +star+ when the set includes + # *, or +nil+ when the set is empty. + def max(star: :*) + (val = @tuples.last&.last) && val == STAR_INT ? star : val + end + + # :call-seq: min(star: :*) => integer or star or nil + # + # Returns the minimum value in +self+, +star+ when the only value in the + # set is *, or +nil+ when the set is empty. + def min(star: :*) + (val = @tuples.first&.first) && val == STAR_INT ? star : val + end + + # :call-seq: minmax(star: :*) => nil or [integer, integer or star] + # + # Returns a 2-element array containing the minimum and maximum numbers in + # +self+, or +nil+ when the set is empty. + def minmax(star: :*); [min(star: star), max(star: star)] unless empty? end + + # Returns false when the set is empty. + def valid?; !empty? end + + # Returns true if the set contains no elements + def empty?; @tuples.empty? end + + # Returns true if the set contains every possible element. + def full?; @tuples == [[1, STAR_INT]] end + + # :call-seq: + # self + other -> sequence set + # self | other -> sequence set + # union(other) -> sequence set + # + # Returns a new sequence set that has every number in the +other+ object + # added. + # + # +other+ may be any object that would be accepted by ::new: a non-zero 32 + # bit unsigned integer, range, sequence-set formatted string, + # another sequence set, or an enumerable containing any of these. + # + # Net::IMAP::SequenceSet["1:5"] | 2 | [4..6, 99] + # #=> Net::IMAP::SequenceSet["1:6,99"] + # + # Related: #add, #merge + def |(other) remain_frozen dup.merge other end + alias :+ :| + alias union :| + + # :call-seq: + # self - other -> sequence set + # difference(other) -> sequence set + # + # Returns a new sequence set built by duplicating this set and removing + # every number that appears in +other+. + # + # +other+ may be any object that would be accepted by ::new: a non-zero 32 + # bit unsigned integer, range, sequence-set formatted string, + # another sequence set, or an enumerable containing any of these. + # + # Net::IMAP::SequenceSet[1..5] - 2 - 4 - 6 + # #=> Net::IMAP::SequenceSet["1,3,5"] + # + # Related: #subtract + def -(other) remain_frozen dup.subtract other end + alias difference :- + + # :call-seq: + # self & other -> sequence set + # intersection(other) -> sequence set + # + # Returns a new sequence set containing only the numbers common to this + # set and +other+. + # + # +other+ may be any object that would be accepted by ::new: a non-zero 32 + # bit unsigned integer, range, sequence-set formatted string, + # another sequence set, or an enumerable containing any of these. + # + # Net::IMAP::SequenceSet[1..5] & [2, 4, 6] + # #=> Net::IMAP::SequenceSet["2,4"] + # + # (seqset & other) is equivalent to (seqset - ~other). + def &(other) + remain_frozen dup.subtract SequenceSet.new(other).complement! + end + alias intersection :& + + # :call-seq: + # self ^ other -> sequence set + # xor(other) -> sequence set + # + # Returns a new sequence set containing numbers that are exclusive between + # this set and +other+. + # + # +other+ may be any object that would be accepted by ::new: a non-zero 32 + # bit unsigned integer, range, sequence-set formatted string, + # another sequence set, or an enumerable containing any of these. + # + # Net::IMAP::SequenceSet[1..5] ^ [2, 4, 6] + # #=> Net::IMAP::SequenceSet["1,3,5:6"] + # + # (seqset ^ other) is equivalent to ((seqset | other) - + # (seqset & other)). + def ^(other) remain_frozen (self | other).subtract(self & other) end + alias xor :^ + + # :call-seq: + # ~ self -> sequence set + # complement -> sequence set + # + # Returns the complement of self, a SequenceSet which contains all numbers + # _except_ for those in this set. + # + # ~Net::IMAP::SequenceSet.full #=> Net::IMAP::SequenceSet.empty + # ~Net::IMAP::SequenceSet.empty #=> Net::IMAP::SequenceSet.full + # ~Net::IMAP::SequenceSet["1:5,100:222"] + # #=> Net::IMAP::SequenceSet["6:99,223:*"] + # ~Net::IMAP::SequenceSet["6:99,223:*"] + # #=> Net::IMAP::SequenceSet["1:5,100:222"] + # + # Related: #complement! + def ~; remain_frozen dup.complement! end + alias complement :~ + + # :call-seq: + # add(object) -> self + # self << other -> self + # + # Adds a range or number to the set and returns +self+. + # + # #string will be regenerated. Use #merge to add many elements at once. + # + # Related: #add?, #merge, #union + def add(object) + tuple_add input_to_tuple object + normalize! + end + alias << add + + # Adds a range or number to the set and returns +self+. + # + # Unlike #add, #merge, or #union, the new value is appended to #string. + # This may result in a #string which has duplicates or is out-of-order. + def append(object) + tuple = input_to_tuple object + entry = tuple_to_str tuple + tuple_add tuple + @string = -(string ? "#{@string},#{entry}" : entry) + self + end + + # :call-seq: add?(object) -> self or nil + # + # Adds a range or number to the set and returns +self+. Returns +nil+ + # when the object is already included in the set. + # + # #string will be regenerated. Use #merge to add many elements at once. + # + # Related: #add, #merge, #union, #include? + def add?(object) + add object unless include? object + end + + # :call-seq: delete(object) -> self + # + # Deletes the given range or number from the set and returns +self+. + # + # #string will be regenerated after deletion. Use #subtract to remove + # many elements at once. + # + # Related: #delete?, #delete_at, #subtract, #difference + def delete(object) + tuple_subtract input_to_tuple object + normalize! + end + + # :call-seq: + # delete?(number) -> integer or nil + # delete?(star) -> :* or nil + # delete?(range) -> sequence set or nil + # + # Removes a specified value from the set, and returns the removed value. + # Returns +nil+ if nothing was removed. + # + # Returns an integer when the specified +number+ argument was removed: + # set = Net::IMAP::SequenceSet.new [5..10, 20] + # set.delete?(7) #=> 7 + # set #=> # + # set.delete?("20") #=> 20 + # set #=> # + # set.delete?(30) #=> nil + # + # Returns :* when * or -1 is specified and + # removed: + # set = Net::IMAP::SequenceSet.new "5:9,20,35,*" + # set.delete?(-1) #=> :* + # set #=> # + # + # And returns a new SequenceSet when a range is specified: + # + # set = Net::IMAP::SequenceSet.new [5..10, 20] + # set.delete?(9..) #=> # + # set #=> # + # set.delete?(21..) #=> nil + # + # #string will be regenerated after deletion. + # + # Related: #delete, #delete_at, #subtract, #difference, #disjoint? + def delete?(object) + tuple = input_to_tuple object + if tuple.first == tuple.last + return unless include_tuple? tuple + tuple_subtract tuple + normalize! + from_tuple_int tuple.first + else + copy = dup + tuple_subtract tuple + normalize! + copy if copy.subtract(self).valid? + end + end + + # :call-seq: delete_at(index) -> number or :* or nil + # + # Deletes a number the set, indicated by the given +index+. Returns the + # number that was removed, or +nil+ if nothing was removed. + # + # #string will be regenerated after deletion. + # + # Related: #delete, #delete?, #slice!, #subtract, #difference + def delete_at(index) + slice! Integer(index.to_int) + end + + # :call-seq: + # slice!(index) -> integer or :* or nil + # slice!(start, length) -> sequence set or nil + # slice!(range) -> sequence set or nil + # + # Deletes a number or consecutive numbers from the set, indicated by the + # given +index+, +start+ and +length+, or +range+ of offsets. Returns the + # number or sequence set that was removed, or +nil+ if nothing was + # removed. Arguments are interpreted the same as for #slice or #[]. + # + # #string will be regenerated after deletion. + # + # Related: #slice, #delete_at, #delete, #delete?, #subtract, #difference + def slice!(index, length = nil) + deleted = slice(index, length) and subtract deleted + deleted + end + + # Merges all of the elements that appear in any of the +inputs+ into the + # set, and returns +self+. + # + # The +inputs+ may be any objects that would be accepted by ::new: + # non-zero 32 bit unsigned integers, ranges, sequence-set + # formatted strings, other sequence sets, or enumerables containing any of + # these. + # + # #string will be regenerated after all inputs have been merged. + # + # Related: #add, #add?, #union + def merge(*inputs) + tuples_add input_to_tuples inputs + normalize! + end + + # Removes all of the elements that appear in any of the given +objects+ + # from the set, and returns +self+. + # + # The +objects+ may be any objects that would be accepted by ::new: + # non-zero 32 bit unsigned integers, ranges, sequence-set + # formatted strings, other sequence sets, or enumerables containing any of + # these. + # + # Related: #difference + def subtract(*objects) + tuples_subtract input_to_tuples objects + normalize! + end + + # Returns an array of ranges and integers and :*. + # + # The entries are in the same order they appear in #string, with no + # sorting, deduplication, or coalescing. When #string is in its + # normalized form, this will return the same result as #elements. + # This is useful when the given order is significant, for example in a + # ESEARCH response to IMAP#sort. + # + # Related: #each_entry, #elements + def entries; each_entry.to_a end + + # Returns an array of ranges and integers and :*. + # + # The returned elements are sorted and coalesced, even when the input + # #string is not. * will sort last. See #normalize. + # + # By itself, * translates to :*. A range containing + # * translates to an endless range. Use #limit to translate both + # cases to a maximum value. + # + # If the original input was unordered or contains overlapping ranges, the + # returned ranges will be ordered and coalesced. + # + # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].elements + # #=> [2, 5..9, 11..12, :*] + # + # Related: #each_element, #ranges, #numbers + def elements; each_element.to_a end + alias to_a elements + + # Returns an array of ranges + # + # The returned elements are sorted and coalesced, even when the input + # #string is not. * will sort last. See #normalize. + # + # * translates to an endless range. By itself, * + # translates to :*... Use #limit to set * to a maximum + # value. + # + # The returned ranges will be ordered and coalesced, even when the input + # #string is not. * will sort last. See #normalize. + # + # Net::IMAP::SequenceSet["2,5:9,6,*,12:11"].ranges + # #=> [2..2, 5..9, 11..12, :*..] + # Net::IMAP::SequenceSet["123,999:*,456:789"].ranges + # #=> [123..123, 456..789, 999..] + # + # Related: #each_range, #elements, #numbers, #to_set + def ranges; each_range.to_a end + + # Returns a sorted array of all of the number values in the sequence set. + # + # The returned numbers are sorted and de-duplicated, even when the input + # #string is not. See #normalize. + # + # Net::IMAP::SequenceSet["2,5:9,6,12:11"].numbers + # #=> [2, 5, 6, 7, 8, 9, 11, 12] + # + # If the set contains a *, RangeError is raised. See #limit. + # + # Net::IMAP::SequenceSet["10000:*"].numbers + # #!> RangeError + # + # *WARNING:* Even excluding sets with *, an enormous result can + # easily be created. An array with over 4 billion integers could be + # returned, requiring up to 32GiB of memory on a 64-bit architecture. + # + # Net::IMAP::SequenceSet[10000..2**32-1].numbers + # # ...probably freezes the process for a while... + # #!> NoMemoryError (probably) + # + # For safety, consider using #limit or #intersection to set an upper + # bound. Alternatively, use #each_element, #each_range, or even + # #each_number to avoid allocation of a result array. + # + # Related: #elements, #ranges, #to_set + def numbers; each_number.to_a end + + # Yields each number or range in #string to the block and returns +self+. + # Returns an enumerator when called without a block. + # + # The entries are yielded in the same order they appear in #tring, with no + # sorting, deduplication, or coalescing. When #string is in its + # normalized form, this will yield the same values as #each_element. + # + # Related: #entries, #each_element + def each_entry(&block) + return to_enum(__method__) unless block_given? + return each_element(&block) unless @string + @string.split(",").each do yield tuple_to_entry str_to_tuple _1 end + self + end + + # Yields each number or range (or :*) in #elements to the block + # and returns self. Returns an enumerator when called without a block. + # + # The returned numbers are sorted and de-duplicated, even when the input + # #string is not. See #normalize. + # + # Related: #elements, #each_entry + def each_element # :yields: integer or range or :* + return to_enum(__method__) unless block_given? + @tuples.each do yield tuple_to_entry _1 end + self + end + + private def tuple_to_entry((min, max)) + if min == STAR_INT then :* + elsif max == STAR_INT then min.. + elsif min == max then min + else min..max + end + end + + # Yields each range in #ranges to the block and returns self. + # Returns an enumerator when called without a block. + # + # Related: #ranges + def each_range # :yields: range + return to_enum(__method__) unless block_given? + @tuples.each do |min, max| + if min == STAR_INT then yield :*.. + elsif max == STAR_INT then yield min.. + else yield min..max + end + end + self + end + + # Yields each number in #numbers to the block and returns self. + # If the set contains a *, RangeError will be raised. + # + # Returns an enumerator when called without a block (even if the set + # contains *). + # + # Related: #numbers + def each_number(&block) # :yields: integer + return to_enum(__method__) unless block_given? + raise RangeError, '%s contains "*"' % [self.class] if include_star? + each_element do |elem| + case elem + when Range then elem.each(&block) + when Integer then block.(elem) + end + end + self + end + + # Returns a Set with all of the #numbers in the sequence set. + # + # If the set contains a *, RangeError will be raised. + # + # See #numbers for the warning about very large sets. + # + # Related: #elements, #ranges, #numbers + def to_set; Set.new(numbers) end + + # Returns the count of #numbers in the set. + # + # If * and 2**32 - 1 (the maximum 32-bit unsigned + # integer value) are both in the set, they will only be counted once. + def count + @tuples.sum(@tuples.count) { _2 - _1 } + + (include_star? && include?(UINT32_MAX) ? -1 : 0) + end + + alias size count + + # Returns the index of +number+ in the set, or +nil+ if +number+ isn't in + # the set. + # + # Related: #[] + def find_index(number) + number = to_tuple_int number + each_tuple_with_index do |min, max, idx_min| + number < min and return nil + number <= max and return from_tuple_int(idx_min + (number - min)) + end + nil + end + + private def each_tuple_with_index + idx_min = 0 + @tuples.each do |min, max| + yield min, max, idx_min, (idx_max = idx_min + (max - min)) + idx_min = idx_max + 1 + end + idx_min + end + + private def reverse_each_tuple_with_index + idx_max = -1 + @tuples.reverse_each do |min, max| + yield min, max, (idx_min = idx_max - (max - min)), idx_max + idx_max = idx_min - 1 + end + idx_max + end + + # :call-seq: at(index) -> integer or nil + # + # Returns a number from +self+, without modifying the set. Behaves the + # same as #[], except that #at only allows a single integer argument. + # + # Related: #[], #slice + def at(index) + index = Integer(index.to_int) + if index.negative? + reverse_each_tuple_with_index do |min, max, idx_min, idx_max| + idx_min <= index and return from_tuple_int(min + (index - idx_min)) + end + else + each_tuple_with_index do |min, _, idx_min, idx_max| + index <= idx_max and return from_tuple_int(min + (index - idx_min)) + end + end + nil + end + + # :call-seq: + # seqset[index] -> integer or :* or nil + # slice(index) -> integer or :* or nil + # seqset[start, length] -> sequence set or nil + # slice(start, length) -> sequence set or nil + # seqset[range] -> sequence set or nil + # slice(range) -> sequence set or nil + # + # Returns a number or a subset from +self+, without modifying the set. + # + # When an Integer argument +index+ is given, the number at offset +index+ + # is returned: + # + # set = Net::IMAP::SequenceSet["10:15,20:23,26"] + # set[0] #=> 10 + # set[5] #=> 15 + # set[10] #=> 26 + # + # If +index+ is negative, it counts relative to the end of +self+: + # set = Net::IMAP::SequenceSet["10:15,20:23,26"] + # set[-1] #=> 26 + # set[-3] #=> 22 + # set[-6] #=> 15 + # + # If +index+ is out of range, +nil+ is returned. + # + # set = Net::IMAP::SequenceSet["10:15,20:23,26"] + # set[11] #=> nil + # set[-12] #=> nil + # + # The result is based on the normalized set—sorted and de-duplicated—not + # on the assigned value of #string. + # + # set = Net::IMAP::SequenceSet["12,20:23,11:16,21"] + # set[0] #=> 11 + # set[-1] #=> 23 + # + def [](index, length = nil) + if length then slice_length(index, length) + elsif index.is_a?(Range) then slice_range(index) + else at(index) + end + end + + alias slice :[] + + private def slice_length(start, length) + start = Integer(start.to_int) + length = Integer(length.to_int) + raise ArgumentError, "length must be positive" unless length.positive? + last = start + length - 1 unless start.negative? && start.abs <= length + slice_range(start..last) + end + + private def slice_range(range) + first = range.begin || 0 + last = range.end || -1 + last -= 1 if range.exclude_end? && range.end && last != STAR_INT + if (first * last).positive? && last < first + SequenceSet.empty + elsif (min = at(first)) + max = at(last) + if max == :* then self & (min..) + elsif min <= max then self & (min..max) + else SequenceSet.empty + end + end + end + + # Returns a frozen SequenceSet with * converted to +max+, numbers + # and ranges over +max+ removed, and ranges containing +max+ converted to + # end at +max+. + # + # Net::IMAP::SequenceSet["5,10:22,50"].limit(max: 20).to_s + # #=> "5,10:20" + # + # * is always interpreted as the maximum value. When the set + # contains *, it will be set equal to the limit. + # + # Net::IMAP::SequenceSet["*"].limit(max: 37) + # #=> Net::IMAP::SequenceSet["37"] + # Net::IMAP::SequenceSet["5:*"].limit(max: 37) + # #=> Net::IMAP::SequenceSet["5:37"] + # Net::IMAP::SequenceSet["500:*"].limit(max: 37) + # #=> Net::IMAP::SequenceSet["37"] + # + def limit(max:) + max = to_tuple_int(max) + if empty? then self.class.empty + elsif !include_star? && max < min then self.class.empty + elsif max(star: STAR_INT) <= max then frozen? ? self : dup.freeze + else dup.limit!(max: max).freeze + end + end + + # Removes all members over +max+ and returns self. If * is a + # member, it will be converted to +max+. + # + # Related: #limit + def limit!(max:) + star = include_star? + max = to_tuple_int(max) + tuple_subtract [max + 1, STAR_INT] + tuple_add [max, max ] if star + normalize! + end + + # :call-seq: complement! -> self + # + # Converts the SequenceSet to its own #complement. It will contain all + # possible values _except_ for those currently in the set. + # + # Related: #complement + def complement! + return replace(self.class.full) if empty? + return clear if full? + flat = @tuples.flat_map { [_1 - 1, _2 + 1] } + if flat.first < 1 then flat.shift else flat.unshift 1 end + if STAR_INT < flat.last then flat.pop else flat.push STAR_INT end + @tuples = flat.each_slice(2).to_a + normalize! + end + + # Returns a new SequenceSet with a normalized string representation. + # + # The returned set's #string is sorted and deduplicated. Adjacent or + # overlapping elements will be merged into a single larger range. + # + # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalize + # #=> Net::IMAP::SequenceSet["1:7,9:11"] + # + # Related: #normalize!, #normalized_string + def normalize + str = normalized_string + return self if frozen? && str == string + remain_frozen dup.instance_exec { @string = str&.-@; self } + end + + # Resets #string to be sorted, deduplicated, and coalesced. Returns + # +self+. + # + # Related: #normalize, #normalized_string + def normalize! + @string = nil + self + end + + # Returns a normalized +sequence-set+ string representation, sorted + # and deduplicated. Adjacent or overlapping elements will be merged into + # a single larger range. Returns +nil+ when the set is empty. + # + # Net::IMAP::SequenceSet["1:5,3:7,10:9,10:11"].normalized_string + # #=> "1:7,9:11" + # + # Related: #normalize!, #normalize + def normalized_string + @tuples.empty? ? nil : -@tuples.map { tuple_to_str _1 }.join(",") + end + + def inspect + if empty? + (frozen? ? "%s.empty" : "#<%s empty>") % [self.class] + elsif frozen? + "%s[%p]" % [self.class, to_s] + else + "#<%s %p>" % [self.class, to_s] + end + end + + # Returns self + alias to_sequence_set itself + + # Unstable API: currently for internal use only (Net::IMAP#validate_data) + def validate # :nodoc: + empty? and raise DataFormatError, "empty sequence-set is invalid" + self + end + + # Unstable API: for internal use only (Net::IMAP#send_data) + def send_data(imap, tag) # :nodoc: + imap.__send__(:put_string, valid_string) + end + + protected + + attr_reader :tuples # :nodoc: + + private + + def remain_frozen(set) frozen? ? set.freeze : set end + + # frozen clones are shallow copied + def initialize_clone(other) + other.frozen? ? super : initialize_dup(other) + end + + def initialize_dup(other) + @tuples = other.tuples.map(&:dup) + @string = other.string&.-@ + super + end + + def input_to_tuple(obj) + obj = input_try_convert obj + case obj + when *STARS, Integer then [int = to_tuple_int(obj), int] + when Range then range_to_tuple(obj) + when String then str_to_tuple(obj) + else + raise DataFormatError, "expected number or range, got %p" % [obj] + end + end + + def input_to_tuples(obj) + obj = input_try_convert obj + case obj + when *STARS, Integer, Range then [input_to_tuple(obj)] + when String then str_to_tuples obj + when SequenceSet then obj.tuples + when ENUMABLE then obj.flat_map { input_to_tuples _1 } + when nil then [] + else + raise DataFormatError, + "expected nz-number, range, string, or enumerable; " \ + "got %p" % [obj] + end + end + + # unlike SequenceSet#try_convert, this returns an Integer, Range, + # String, Set, Array, or... any type of object. + def input_try_convert(input) + SequenceSet.try_convert(input) || + # Integer.try_convert(input) || # ruby 3.1+ + input.respond_to?(:to_int) && Integer(input.to_int) || + String.try_convert(input) || + input + end + + def range_to_tuple(range) + first = to_tuple_int(range.begin || 1) + last = to_tuple_int(range.end || :*) + last -= 1 if range.exclude_end? && range.end && last != STAR_INT + unless first <= last + raise DataFormatError, "invalid range for sequence-set: %p" % [range] + end + [first, last] + end + + def to_tuple_int(obj) STARS.include?(obj) ? STAR_INT : nz_number(obj) end + def from_tuple_int(num) num == STAR_INT ? :* : num end + + def tuple_to_str(tuple) tuple.uniq.map{ from_tuple_int _1 }.join(":") end + def str_to_tuples(str) str.split(",", -1).map! { str_to_tuple _1 } end + def str_to_tuple(str) + raise DataFormatError, "invalid sequence set string" if str.empty? + str.split(":", 2).map! { to_tuple_int _1 }.minmax + end + + def include_tuple?((min, max)) range_gte_to(min)&.cover?(min..max) end + + def intersect_tuple?((min, max)) + range = range_gte_to(min) and + range.include?(min) || range.include?(max) || (min..max).cover?(range) + end + + def tuples_add(tuples) tuples.each do tuple_add _1 end; self end + def tuples_subtract(tuples) tuples.each do tuple_subtract _1 end; self end + + # + # --|=====| |=====new tuple=====| append + # ?????????-|=====new tuple=====|-|===lower===|-- insert + # + # |=====new tuple=====| + # ---------??=======lower=======??--------------- noop + # + # ---------??===lower==|--|==| join remaining + # ---------??===lower==|--|==|----|===upper===|-- join until upper + # ---------??===lower==|--|==|--|=====upper===|-- join to upper + def tuple_add(tuple) + min, max = tuple + lower, lower_idx = tuple_gte_with_index(min - 1) + if lower.nil? then tuples << tuple + elsif (max + 1) < lower.first then tuples.insert(lower_idx, tuple) + else tuple_coalesce(lower, lower_idx, min, max) + end + end + + def tuple_coalesce(lower, lower_idx, min, max) + return if lower.first <= min && max <= lower.last + lower[0] = [min, lower.first].min + lower[1] = [max, lower.last].max + lower_idx += 1 + return if lower_idx == tuples.count + tmax_adj = lower.last + 1 + upper, upper_idx = tuple_gte_with_index(tmax_adj) + if upper + tmax_adj < upper.first ? (upper_idx -= 1) : (lower[1] = upper.last) + end + tuples.slice!(lower_idx..upper_idx) + end + + # |====tuple================| + # --|====| no more 1. noop + # --|====|---------------------------|====lower====|-- 2. noop + # -------|======lower================|---------------- 3. split + # --------|=====lower================|---------------- 4. trim beginning + # + # -------|======lower====????????????----------------- trim lower + # --------|=====lower====????????????----------------- delete lower + # + # -------??=====lower===============|----------------- 5. trim/delete one + # -------??=====lower====|--|====| no more 6. delete rest + # -------??=====lower====|--|====|---|====upper====|-- 7. delete until + # -------??=====lower====|--|====|--|=====upper====|-- 8. delete and trim + def tuple_subtract(tuple) + min, max = tuple + lower, idx = tuple_gte_with_index(min) + if lower.nil? then nil # case 1. + elsif max < lower.first then nil # case 2. + elsif max < lower.last then tuple_trim_or_split lower, idx, min, max + else tuples_trim_or_delete lower, idx, min, max + end + end + + def tuple_trim_or_split(lower, idx, tmin, tmax) + if lower.first < tmin # split + tuples.insert(idx, [lower.first, tmin - 1]) + end + lower[0] = tmax + 1 + end + + def tuples_trim_or_delete(lower, lower_idx, tmin, tmax) + if lower.first < tmin # trim lower + lower[1] = tmin - 1 + lower_idx += 1 + end + if tmax == lower.last # case 5 + upper_idx = lower_idx + elsif (upper, upper_idx = tuple_gte_with_index(tmax + 1)) + upper_idx -= 1 # cases 7 and 8 + upper[0] = tmax + 1 if upper.first <= tmax # case 8 (else case 7) + end + tuples.slice!(lower_idx..upper_idx) + end + + def tuple_gte_with_index(num) + idx = tuples.bsearch_index { _2 >= num } and [tuples[idx], idx] + end + + def range_gte_to(num) + first, last = tuples.bsearch { _2 >= num } + first..last if first + end + + def nz_number(num) + case num + when Integer, /\A[1-9]\d*\z/ then num = Integer(num) + else raise DataFormatError, "%p is not a valid nz-number" % [num] + end + NumValidator.ensure_nz_number(num) + num + end + + # intentionally defined after the class implementation + + EMPTY = new.freeze + FULL = self["1:*"] + private_constant :EMPTY, :FULL + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/nameprep.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/nameprep.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/nameprep.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/nameprep.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Net + class IMAP + module StringPrep + + # Defined in RFC3491[https://tools.ietf.org/html/rfc3491], the +nameprep+ + # profile of "Stringprep" is: + # >>> + # used by the IDNA protocol for preparing domain names; it is not + # designed for any other purpose. It is explicitly not designed for + # processing arbitrary free text and SHOULD NOT be used for that + # purpose. + # + # ... + # + # This profile specifies prohibiting using the following tables...: + # + # - C.1.2 (Non-ASCII space characters) + # - C.2.2 (Non-ASCII control characters) + # - C.3 (Private use characters) + # - C.4 (Non-character code points) + # - C.5 (Surrogate codes) + # - C.6 (Inappropriate for plain text) + # - C.7 (Inappropriate for canonical representation) + # - C.8 (Change display properties are deprecated) + # - C.9 (Tagging characters) + # + # IMPORTANT NOTE: This profile MUST be used with the IDNA protocol. + # The IDNA protocol has additional prohibitions that are checked + # outside of this profile. + module NamePrep + + # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §10 + STRINGPREP_PROFILE = "nameprep" + + # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §2 + UNASSIGNED_TABLE = "A.1" + + # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §3 + MAPPING_TABLES = %w[B.1 B.2].freeze + + # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §4 + NORMALIZATION = :nfkc + + # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §5 + PROHIBITED_TABLES = %w[C.1.2 C.2.2 C.3 C.4 C.5 C.6 C.7 C.8 C.9].freeze + + # From RFC3491[https://www.rfc-editor.org/rfc/rfc3491.html] §6 + CHECK_BIDI = true + + module_function + + def nameprep(string, **opts) + StringPrep.stringprep( + string, + unassigned: UNASSIGNED_TABLE, + maps: MAPPING_TABLES, + prohibited: PROHIBITED_TABLES, + normalization: NORMALIZATION, + bidi: CHECK_BIDI, + profile: STRINGPREP_PROFILE, + **opts, + ) + end + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/saslprep.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/saslprep.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/saslprep.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/saslprep.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Net + class IMAP + module StringPrep + + # SASLprep#saslprep can be used to prepare a string according to [RFC4013]. + # + # \SASLprep maps characters three ways: to nothing, to space, and Unicode + # normalization form KC. \SASLprep prohibits codepoints from nearly all + # standard StringPrep tables (RFC3454, Appendix "C"), and uses + # \StringPrep's standard bidirectional characters requirements (Appendix + # "D"). \SASLprep also uses \StringPrep's definition of "Unassigned" + # codepoints (Appendix "A"). + module SASLprep + + # Used to short-circuit strings that don't need preparation. + ASCII_NO_CTRLS = /\A[\x20-\x7e]*\z/u.freeze + + # Avoid loading these tables unless they are needed (they are only + # needed for non-ASCII). + saslprep_tables = File.expand_path("saslprep_tables", __dir__) + autoload :MAP_TO_NOTHING, saslprep_tables + autoload :MAP_TO_SPACE, saslprep_tables + autoload :PROHIBITED, saslprep_tables + autoload :PROHIBITED_STORED, saslprep_tables + autoload :TABLES_PROHIBITED, saslprep_tables + autoload :TABLES_PROHIBITED_STORED, saslprep_tables + + module_function + + # Prepares a UTF-8 +string+ for comparison, using the \SASLprep profile + # RFC4013 of the StringPrep algorithm RFC3454. + # + # By default, prohibited strings will return +nil+. When +exception+ is + # +true+, a StringPrepError describing the violation will be raised. + # + # When +stored+ is +true+, "unassigned" codepoints will be prohibited. + # For \StringPrep and the \SASLprep profile, "unassigned" refers to + # Unicode 3.2, and not later versions. See RFC3454 §7 for more + # information. + def saslprep(str, stored: false, exception: false) + return str if ASCII_NO_CTRLS.match?(str) # incompatible encoding raises + str = str.encode("UTF-8") # also dups (and raises for invalid encoding) + str.gsub!(MAP_TO_SPACE, " ") + str.gsub!(MAP_TO_NOTHING, "") + str.unicode_normalize!(:nfkc) + # These regexps combine the prohibited and bidirectional checks + return str unless str.match?(stored ? PROHIBITED_STORED : PROHIBITED) + return nil unless exception + # raise helpful errors to indicate *why* it failed: + tables = stored ? TABLES_PROHIBITED_STORED : TABLES_PROHIBITED + StringPrep.check_prohibited! str, *tables, bidi: true, profile: "SASLprep" + raise InvalidStringError.new( + "unknown error", string: string, profile: "SASLprep" + ) + rescue ArgumentError, Encoding::CompatibilityError => ex + if /invalid byte sequence|incompatible encoding/.match? ex.message + return nil unless exception + raise StringPrepError.new(ex.message, string: str, profile: "saslprep") + end + raise ex + end + + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/saslprep_tables.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/saslprep_tables.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/saslprep_tables.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/saslprep_tables.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +#-- +# This file is generated from RFC3454, by rake. Don't edit directly. +#++ + +module Net::IMAP::StringPrep + + module SASLprep + + # RFC4013 §2.1 Mapping - mapped to space + # >>> + # non-ASCII space characters (\StringPrep\[\"C.1.2\"]) that can + # be mapped to SPACE (U+0020) + # + # Equal to \StringPrep\[\"C.1.2\"]. + # Redefined here to avoid loading StringPrep::Tables unless necessary. + MAP_TO_SPACE = /[\u200b\p{Zs}&&[^ ]]/.freeze + + # RFC4013 §2.1 Mapping - mapped to nothing + # >>> + # the "commonly mapped to nothing" characters + # (\StringPrep\[\"B.1\"]) that can be mapped to nothing. + # + # Equal to \StringPrep\[\"B.1\"]. + # Redefined here to avoid loading StringPrep::Tables unless necessary. + MAP_TO_NOTHING = /[\u{00ad 034f 1806 2060 feff}\u{180b}-\u{180d}\u{200b}-\u{200d}\u{fe00}-\u{fe0f}]/.freeze + + # RFC4013 §2.3 Prohibited Output + # >>> + # * Non-ASCII space characters — \StringPrep\[\"C.1.2\"] + # * ASCII control characters — \StringPrep\[\"C.2.1\"] + # * Non-ASCII control characters — \StringPrep\[\"C.2.2\"] + # * Private Use characters — \StringPrep\[\"C.3\"] + # * Non-character code points — \StringPrep\[\"C.4\"] + # * Surrogate code points — \StringPrep\[\"C.5\"] + # * Inappropriate for plain text characters — \StringPrep\[\"C.6\"] + # * Inappropriate for canonical representation characters — \StringPrep\[\"C.7\"] + # * Change display properties or deprecated characters — \StringPrep\[\"C.8\"] + # * Tagging characters — \StringPrep\[\"C.9\"] + TABLES_PROHIBITED = ["C.1.2", "C.2.1", "C.2.2", "C.3", "C.4", "C.5", "C.6", "C.7", "C.8", "C.9"].freeze + + # Adds unassigned (by Unicode 3.2) codepoints to TABLES_PROHIBITED. + # + # RFC4013 §2.5 Unassigned Code Points + # >>> + # This profile specifies the \StringPrep\[\"A.1\"] table as its + # list of unassigned code points. + TABLES_PROHIBITED_STORED = ["A.1", *TABLES_PROHIBITED].freeze + + # A Regexp matching codepoints prohibited by RFC4013 §2.3. + # + # This combines all of the TABLES_PROHIBITED tables. + PROHIBITED_OUTPUT = /[\u{06dd 070f 1680 180e 3000 feff e0001}\u{0000}-\u{001f}\u{007f}-\u{00a0}\u{0340}-\u{0341}\u{2000}-\u{200f}\u{2028}-\u{202f}\u{205f}-\u{2063}\u{206a}-\u{206f}\u{2ff0}-\u{2ffb}\u{e000}-\u{f8ff}\u{fdd0}-\u{fdef}\u{fff9}-\u{ffff}\u{1d173}-\u{1d17a}\u{1fffe}-\u{1ffff}\u{2fffe}-\u{2ffff}\u{3fffe}-\u{3ffff}\u{4fffe}-\u{4ffff}\u{5fffe}-\u{5ffff}\u{6fffe}-\u{6ffff}\u{7fffe}-\u{7ffff}\u{8fffe}-\u{8ffff}\u{9fffe}-\u{9ffff}\u{afffe}-\u{affff}\u{bfffe}-\u{bffff}\u{cfffe}-\u{cffff}\u{dfffe}-\u{dffff}\u{e0020}-\u{e007f}\u{efffe}-\u{10ffff}\p{Cs}]/.freeze + + # RFC4013 §2.5 Unassigned Code Points + # >>> + # This profile specifies the \StringPrep\[\"A.1\"] table as its + # list of unassigned code points. + # + # Equal to \StringPrep\[\"A.1\"]. + # Redefined here to avoid loading StringPrep::Tables unless necessary. + UNASSIGNED = /\p{^AGE=3.2}/.freeze + + # A Regexp matching codepoints prohibited by RFC4013 §2.3 and §2.5. + # + # This combines PROHIBITED_OUTPUT and UNASSIGNED. + PROHIBITED_OUTPUT_STORED = Regexp.union( + UNASSIGNED, PROHIBITED_OUTPUT + ).freeze + + # Bidirectional Characters [StringPrep, §6] + # + # A Regexp for strings that don't satisfy StringPrep's Bidirectional + # Characters rules. + # + # Equal to StringPrep::Tables::BIDI_FAILURE. + # Redefined here to avoid loading StringPrep::Tables unless necessary. + BIDI_FAILURE = /(?-mix:(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]))|(?m-ix:(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])))|(?-mix:(?m-ix:\A(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))|(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])\z))/.freeze + + # A Regexp matching strings prohibited by RFC4013 §2.3 and §2.4. + # + # This combines PROHIBITED_OUTPUT and BIDI_FAILURE. + PROHIBITED = Regexp.union( + PROHIBITED_OUTPUT, BIDI_FAILURE, + ) + + # A Regexp matching strings prohibited by RFC4013 §2.3, §2.4, and §2.5. + # + # This combines PROHIBITED_OUTPUT_STORED and BIDI_FAILURE. + PROHIBITED_STORED = Regexp.union( + PROHIBITED_OUTPUT_STORED, BIDI_FAILURE, + ) + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/tables.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/tables.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/tables.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/tables.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +#-- +# This file is generated from RFC3454, by rake. Don't edit directly. +#++ + +module Net::IMAP::StringPrep + + module Tables + + # Unassigned code points in Unicode 3.2 \StringPrep\[\"A.1\"] + IN_A_1 = /\p{^AGE=3.2}/.freeze + + # Commonly mapped to nothing \StringPrep\[\"B.1\"] + IN_B_1 = /[\u{00ad 034f 1806 2060 feff}\u{180b}-\u{180d}\u{200b}-\u{200d}\u{fe00}-\u{fe0f}]/.freeze + + # Mapping for case-folding used with NFKC \StringPrep\[\"B.2\"] + IN_B_2 = /[\u{00b5 0100 0102 0104 0106 0108 010a 010c 010e 0110 0112 0114 0116 0118 011a 011c 011e 0120 0122 0124 0126 0128 012a 012c 012e 0130 0132 0134 0136 0139 013b 013d 013f 0141 0143 0145 0147 014c 014e 0150 0152 0154 0156 0158 015a 015c 015e 0160 0162 0164 0166 0168 016a 016c 016e 0170 0172 0174 0176 017b 017d 017f 0184 01a2 01a4 01a9 01ac 01b5 01bc 01cd 01cf 01d1 01d3 01d5 01d7 01d9 01db 01de 01e0 01e2 01e4 01e6 01e8 01ea 01ec 01ee 01f4 01fa 01fc 01fe 0200 0202 0204 0206 0208 020a 020c 020e 0210 0212 0214 0216 0218 021a 021c 021e 0220 0222 0224 0226 0228 022a 022c 022e 0230 0232 0345 037a 0386 038c 03b0 03c2 03d8 03da 03dc 03de 03e0 03e2 03e4 03e6 03e8 03ea 03ec 03ee 0460 0462 0464 0466 0468 046a 046c 046e 0470 0472 0474 0476 0478 047a 047c 047e 0480 048a 048c 048e 0490 0492 0494 0496 0498 049a 049c 049e 04a0 04a2 04a4 04a6 04a8 04aa 04ac 04ae 04b0 04b2 04b4 04b6 04b8 04ba 04bc 04be 04c1 04c3 04c5 04c7 04c9 04cb 04cd 04d0 04d2 04d4 04d6 04d8 04da 04dc 04de 04e0 04e2 04e4 04e6 04e8 04ea 04ec 04ee 04f0 04f2 04f4 04f8 0500 0502 0504 0506 0508 050a 050c 050e 0587 1e00 1e02 1e04 1e06 1e08 1e0a 1e0c 1e0e 1e10 1e12 1e14 1e16 1e18 1e1a 1e1c 1e1e 1e20 1e22 1e24 1e26 1e28 1e2a 1e2c 1e2e 1e30 1e32 1e34 1e36 1e38 1e3a 1e3c 1e3e 1e40 1e42 1e44 1e46 1e48 1e4a 1e4c 1e4e 1e50 1e52 1e54 1e56 1e58 1e5a 1e5c 1e5e 1e60 1e62 1e64 1e66 1e68 1e6a 1e6c 1e6e 1e70 1e72 1e74 1e76 1e78 1e7a 1e7c 1e7e 1e80 1e82 1e84 1e86 1e88 1e8a 1e8c 1e8e 1e90 1e92 1e94 1ea0 1ea2 1ea4 1ea6 1ea8 1eaa 1eac 1eae 1eb0 1eb2 1eb4 1eb6 1eb8 1eba 1ebc 1ebe 1ec0 1ec2 1ec4 1ec6 1ec8 1eca 1ecc 1ece 1ed0 1ed2 1ed4 1ed6 1ed8 1eda 1edc 1ede 1ee0 1ee2 1ee4 1ee6 1ee8 1eea 1eec 1eee 1ef0 1ef2 1ef4 1ef6 1ef8 1f50 1f52 1f54 1f56 1f59 1f5b 1f5d 1f5f 1fbe 20a8 2107 2109 2124 2126 2128 2133 2145 3371 3373 3375 33c3 33cb 33d7 1d49c 1d4a2 1d546 1d6d3 1d70d 1d747 1d781 1d7bb}\u{0041}-\u{005a}\u{00c0}-\u{00d6}\u{00d8}-\u{00df}\u{0149}-\u{014a}\u{0178}-\u{0179}\u{0181}-\u{0182}\u{0186}-\u{0187}\u{0189}-\u{018b}\u{018e}-\u{0191}\u{0193}-\u{0194}\u{0196}-\u{0198}\u{019c}-\u{019d}\u{019f}-\u{01a0}\u{01a6}-\u{01a7}\u{01ae}-\u{01af}\u{01b1}-\u{01b3}\u{01b7}-\u{01b8}\u{01c4}-\u{01c5}\u{01c7}-\u{01c8}\u{01ca}-\u{01cb}\u{01f0}-\u{01f2}\u{01f6}-\u{01f8}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ab}\u{03d0}-\u{03d6}\u{03f0}-\u{03f2}\u{03f4}-\u{03f5}\u{0400}-\u{042f}\u{0531}-\u{0556}\u{1e96}-\u{1e9b}\u{1f08}-\u{1f0f}\u{1f18}-\u{1f1d}\u{1f28}-\u{1f2f}\u{1f38}-\u{1f3f}\u{1f48}-\u{1f4d}\u{1f68}-\u{1f6f}\u{1f80}-\u{1faf}\u{1fb2}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd2}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe2}-\u{1fe4}\u{1fe6}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{2102}-\u{2103}\u{210b}-\u{210d}\u{2110}-\u{2112}\u{2115}-\u{2116}\u{2119}-\u{211d}\u{2120}-\u{2122}\u{212a}-\u{212d}\u{2130}-\u{2131}\u{213e}-\u{213f}\u{2160}-\u{216f}\u{24b6}-\u{24cf}\u{3380}-\u{3387}\u{338a}-\u{338c}\u{3390}-\u{3394}\u{33a9}-\u{33ac}\u{33b4}-\u{33c1}\u{33c6}-\u{33c9}\u{33cd}-\u{33ce}\u{33d9}-\u{33da}\u{33dc}-\u{33dd}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{10400}-\u{10425}\u{1d400}-\u{1d419}\u{1d434}-\u{1d44d}\u{1d468}-\u{1d481}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b5}\u{1d4d0}-\u{1d4e9}\u{1d504}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d538}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d56c}-\u{1d585}\u{1d5a0}-\u{1d5b9}\u{1d5d4}-\u{1d5ed}\u{1d608}-\u{1d621}\u{1d63c}-\u{1d655}\u{1d670}-\u{1d689}\u{1d6a8}-\u{1d6c0}\u{1d6e2}-\u{1d6fa}\u{1d71c}-\u{1d734}\u{1d756}-\u{1d76e}\u{1d790}-\u{1d7a8}]/.freeze + + # Mapping for case-folding used with no normalization \StringPrep\[\"B.3\"] + IN_B_3 = /[\u{00b5 0100 0102 0104 0106 0108 010a 010c 010e 0110 0112 0114 0116 0118 011a 011c 011e 0120 0122 0124 0126 0128 012a 012c 012e 0130 0132 0134 0136 0139 013b 013d 013f 0141 0143 0145 0147 014c 014e 0150 0152 0154 0156 0158 015a 015c 015e 0160 0162 0164 0166 0168 016a 016c 016e 0170 0172 0174 0176 017b 017d 017f 0184 01a2 01a4 01a9 01ac 01b5 01bc 01cd 01cf 01d1 01d3 01d5 01d7 01d9 01db 01de 01e0 01e2 01e4 01e6 01e8 01ea 01ec 01ee 01f4 01fa 01fc 01fe 0200 0202 0204 0206 0208 020a 020c 020e 0210 0212 0214 0216 0218 021a 021c 021e 0220 0222 0224 0226 0228 022a 022c 022e 0230 0232 0345 0386 038c 03b0 03c2 03d8 03da 03dc 03de 03e0 03e2 03e4 03e6 03e8 03ea 03ec 03ee 0460 0462 0464 0466 0468 046a 046c 046e 0470 0472 0474 0476 0478 047a 047c 047e 0480 048a 048c 048e 0490 0492 0494 0496 0498 049a 049c 049e 04a0 04a2 04a4 04a6 04a8 04aa 04ac 04ae 04b0 04b2 04b4 04b6 04b8 04ba 04bc 04be 04c1 04c3 04c5 04c7 04c9 04cb 04cd 04d0 04d2 04d4 04d6 04d8 04da 04dc 04de 04e0 04e2 04e4 04e6 04e8 04ea 04ec 04ee 04f0 04f2 04f4 04f8 0500 0502 0504 0506 0508 050a 050c 050e 0587 1e00 1e02 1e04 1e06 1e08 1e0a 1e0c 1e0e 1e10 1e12 1e14 1e16 1e18 1e1a 1e1c 1e1e 1e20 1e22 1e24 1e26 1e28 1e2a 1e2c 1e2e 1e30 1e32 1e34 1e36 1e38 1e3a 1e3c 1e3e 1e40 1e42 1e44 1e46 1e48 1e4a 1e4c 1e4e 1e50 1e52 1e54 1e56 1e58 1e5a 1e5c 1e5e 1e60 1e62 1e64 1e66 1e68 1e6a 1e6c 1e6e 1e70 1e72 1e74 1e76 1e78 1e7a 1e7c 1e7e 1e80 1e82 1e84 1e86 1e88 1e8a 1e8c 1e8e 1e90 1e92 1e94 1ea0 1ea2 1ea4 1ea6 1ea8 1eaa 1eac 1eae 1eb0 1eb2 1eb4 1eb6 1eb8 1eba 1ebc 1ebe 1ec0 1ec2 1ec4 1ec6 1ec8 1eca 1ecc 1ece 1ed0 1ed2 1ed4 1ed6 1ed8 1eda 1edc 1ede 1ee0 1ee2 1ee4 1ee6 1ee8 1eea 1eec 1eee 1ef0 1ef2 1ef4 1ef6 1ef8 1f50 1f52 1f54 1f56 1f59 1f5b 1f5d 1f5f 1fbe 2126}\u{0041}-\u{005a}\u{00c0}-\u{00d6}\u{00d8}-\u{00df}\u{0149}-\u{014a}\u{0178}-\u{0179}\u{0181}-\u{0182}\u{0186}-\u{0187}\u{0189}-\u{018b}\u{018e}-\u{0191}\u{0193}-\u{0194}\u{0196}-\u{0198}\u{019c}-\u{019d}\u{019f}-\u{01a0}\u{01a6}-\u{01a7}\u{01ae}-\u{01af}\u{01b1}-\u{01b3}\u{01b7}-\u{01b8}\u{01c4}-\u{01c5}\u{01c7}-\u{01c8}\u{01ca}-\u{01cb}\u{01f0}-\u{01f2}\u{01f6}-\u{01f8}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ab}\u{03d0}-\u{03d1}\u{03d5}-\u{03d6}\u{03f0}-\u{03f2}\u{03f4}-\u{03f5}\u{0400}-\u{042f}\u{0531}-\u{0556}\u{1e96}-\u{1e9b}\u{1f08}-\u{1f0f}\u{1f18}-\u{1f1d}\u{1f28}-\u{1f2f}\u{1f38}-\u{1f3f}\u{1f48}-\u{1f4d}\u{1f68}-\u{1f6f}\u{1f80}-\u{1faf}\u{1fb2}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd2}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe2}-\u{1fe4}\u{1fe6}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{212a}-\u{212b}\u{2160}-\u{216f}\u{24b6}-\u{24cf}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{10400}-\u{10425}]/.freeze + + # Replacements for IN_B.1 + MAP_B_1 = "".freeze + + # Replacements for IN_B.2 + MAP_B_2 = {"A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e", "F"=>"f", "G"=>"g", "H"=>"h", "I"=>"i", "J"=>"j", "K"=>"k", "L"=>"l", "M"=>"m", "N"=>"n", "O"=>"o", "P"=>"p", "Q"=>"q", "R"=>"r", "S"=>"s", "T"=>"t", "U"=>"u", "V"=>"v", "W"=>"w", "X"=>"x", "Y"=>"y", "Z"=>"z", "µ"=>"μ", "À"=>"à", "Á"=>"á", "Â"=>"â", "Ã"=>"ã", "Ä"=>"ä", "Å"=>"å", "Æ"=>"æ", "Ç"=>"ç", "È"=>"è", "É"=>"é", "Ê"=>"ê", "Ë"=>"ë", "Ì"=>"ì", "Í"=>"í", "Î"=>"î", "Ï"=>"ï", "Ð"=>"ð", "Ñ"=>"ñ", "Ò"=>"ò", "Ó"=>"ó", "Ô"=>"ô", "Õ"=>"õ", "Ö"=>"ö", "Ø"=>"ø", "Ù"=>"ù", "Ú"=>"ú", "Û"=>"û", "Ü"=>"ü", "Ý"=>"ý", "Þ"=>"þ", "ß"=>"ss", "Ā"=>"ā", "Ă"=>"ă", "Ą"=>"ą", "Ć"=>"ć", "Ĉ"=>"ĉ", "Ċ"=>"ċ", "Č"=>"č", "Ď"=>"ď", "Đ"=>"đ", "Ē"=>"ē", "Ĕ"=>"ĕ", "Ė"=>"ė", "Ę"=>"ę", "Ě"=>"ě", "Ĝ"=>"ĝ", "Ğ"=>"ğ", "Ġ"=>"ġ", "Ģ"=>"ģ", "Ĥ"=>"ĥ", "Ħ"=>"ħ", "Ĩ"=>"ĩ", "Ī"=>"ī", "Ĭ"=>"ĭ", "Į"=>"į", "İ"=>"i̇", "IJ"=>"ij", "Ĵ"=>"ĵ", "Ķ"=>"ķ", "Ĺ"=>"ĺ", "Ļ"=>"ļ", "Ľ"=>"ľ", "Ŀ"=>"ŀ", "Ł"=>"ł", "Ń"=>"ń", "Ņ"=>"ņ", "Ň"=>"ň", "ʼn"=>"ʼn", "Ŋ"=>"ŋ", "Ō"=>"ō", "Ŏ"=>"ŏ", "Ő"=>"ő", "Œ"=>"œ", "Ŕ"=>"ŕ", "Ŗ"=>"ŗ", "Ř"=>"ř", "Ś"=>"ś", "Ŝ"=>"ŝ", "Ş"=>"ş", "Š"=>"š", "Ţ"=>"ţ", "Ť"=>"ť", "Ŧ"=>"ŧ", "Ũ"=>"ũ", "Ū"=>"ū", "Ŭ"=>"ŭ", "Ů"=>"ů", "Ű"=>"ű", "Ų"=>"ų", "Ŵ"=>"ŵ", "Ŷ"=>"ŷ", "Ÿ"=>"ÿ", "Ź"=>"ź", "Ż"=>"ż", "Ž"=>"ž", "ſ"=>"s", "Ɓ"=>"ɓ", "Ƃ"=>"ƃ", "Ƅ"=>"ƅ", "Ɔ"=>"ɔ", "Ƈ"=>"ƈ", "Ɖ"=>"ɖ", "Ɗ"=>"ɗ", "Ƌ"=>"ƌ", "Ǝ"=>"ǝ", "Ə"=>"ə", "Ɛ"=>"ɛ", "Ƒ"=>"ƒ", "Ɠ"=>"ɠ", "Ɣ"=>"ɣ", "Ɩ"=>"ɩ", "Ɨ"=>"ɨ", "Ƙ"=>"ƙ", "Ɯ"=>"ɯ", "Ɲ"=>"ɲ", "Ɵ"=>"ɵ", "Ơ"=>"ơ", "Ƣ"=>"ƣ", "Ƥ"=>"ƥ", "Ʀ"=>"ʀ", "Ƨ"=>"ƨ", "Ʃ"=>"ʃ", "Ƭ"=>"ƭ", "Ʈ"=>"ʈ", "Ư"=>"ư", "Ʊ"=>"ʊ", "Ʋ"=>"ʋ", "Ƴ"=>"ƴ", "Ƶ"=>"ƶ", "Ʒ"=>"ʒ", "Ƹ"=>"ƹ", "Ƽ"=>"ƽ", "DŽ"=>"dž", "Dž"=>"dž", "LJ"=>"lj", "Lj"=>"lj", "NJ"=>"nj", "Nj"=>"nj", "Ǎ"=>"ǎ", "Ǐ"=>"ǐ", "Ǒ"=>"ǒ", "Ǔ"=>"ǔ", "Ǖ"=>"ǖ", "Ǘ"=>"ǘ", "Ǚ"=>"ǚ", "Ǜ"=>"ǜ", "Ǟ"=>"ǟ", "Ǡ"=>"ǡ", "Ǣ"=>"ǣ", "Ǥ"=>"ǥ", "Ǧ"=>"ǧ", "Ǩ"=>"ǩ", "Ǫ"=>"ǫ", "Ǭ"=>"ǭ", "Ǯ"=>"ǯ", "ǰ"=>"ǰ", "DZ"=>"dz", "Dz"=>"dz", "Ǵ"=>"ǵ", "Ƕ"=>"ƕ", "Ƿ"=>"ƿ", "Ǹ"=>"ǹ", "Ǻ"=>"ǻ", "Ǽ"=>"ǽ", "Ǿ"=>"ǿ", "Ȁ"=>"ȁ", "Ȃ"=>"ȃ", "Ȅ"=>"ȅ", "Ȇ"=>"ȇ", "Ȉ"=>"ȉ", "Ȋ"=>"ȋ", "Ȍ"=>"ȍ", "Ȏ"=>"ȏ", "Ȑ"=>"ȑ", "Ȓ"=>"ȓ", "Ȕ"=>"ȕ", "Ȗ"=>"ȗ", "Ș"=>"ș", "Ț"=>"ț", "Ȝ"=>"ȝ", "Ȟ"=>"ȟ", "Ƞ"=>"ƞ", "Ȣ"=>"ȣ", "Ȥ"=>"ȥ", "Ȧ"=>"ȧ", "Ȩ"=>"ȩ", "Ȫ"=>"ȫ", "Ȭ"=>"ȭ", "Ȯ"=>"ȯ", "Ȱ"=>"ȱ", "Ȳ"=>"ȳ", "ͅ"=>"ι", "ͺ"=>" ι", "Ά"=>"ά", "Έ"=>"έ", "Ή"=>"ή", "Ί"=>"ί", "Ό"=>"ό", "Ύ"=>"ύ", "Ώ"=>"ώ", "ΐ"=>"ΐ", "Α"=>"α", "Β"=>"β", "Γ"=>"γ", "Δ"=>"δ", "Ε"=>"ε", "Ζ"=>"ζ", "Η"=>"η", "Θ"=>"θ", "Ι"=>"ι", "Κ"=>"κ", "Λ"=>"λ", "Μ"=>"μ", "Ν"=>"ν", "Ξ"=>"ξ", "Ο"=>"ο", "Π"=>"π", "Ρ"=>"ρ", "Σ"=>"σ", "Τ"=>"τ", "Υ"=>"υ", "Φ"=>"φ", "Χ"=>"χ", "Ψ"=>"ψ", "Ω"=>"ω", "Ϊ"=>"ϊ", "Ϋ"=>"ϋ", "ΰ"=>"ΰ", "ς"=>"σ", "ϐ"=>"β", "ϑ"=>"θ", "ϒ"=>"υ", "ϓ"=>"ύ", "ϔ"=>"ϋ", "ϕ"=>"φ", "ϖ"=>"π", "Ϙ"=>"ϙ", "Ϛ"=>"ϛ", "Ϝ"=>"ϝ", "Ϟ"=>"ϟ", "Ϡ"=>"ϡ", "Ϣ"=>"ϣ", "Ϥ"=>"ϥ", "Ϧ"=>"ϧ", "Ϩ"=>"ϩ", "Ϫ"=>"ϫ", "Ϭ"=>"ϭ", "Ϯ"=>"ϯ", "ϰ"=>"κ", "ϱ"=>"ρ", "ϲ"=>"σ", "ϴ"=>"θ", "ϵ"=>"ε", "Ѐ"=>"ѐ", "Ё"=>"ё", "Ђ"=>"ђ", "Ѓ"=>"ѓ", "Є"=>"є", "Ѕ"=>"ѕ", "І"=>"і", "Ї"=>"ї", "Ј"=>"ј", "Љ"=>"љ", "Њ"=>"њ", "Ћ"=>"ћ", "Ќ"=>"ќ", "Ѝ"=>"ѝ", "Ў"=>"ў", "Џ"=>"џ", "А"=>"а", "Б"=>"б", "В"=>"в", "Г"=>"г", "Д"=>"д", "Е"=>"е", "Ж"=>"ж", "З"=>"з", "И"=>"и", "Й"=>"й", "К"=>"к", "Л"=>"л", "М"=>"м", "Н"=>"н", "О"=>"о", "П"=>"п", "Р"=>"р", "С"=>"с", "Т"=>"т", "У"=>"у", "Ф"=>"ф", "Х"=>"х", "Ц"=>"ц", "Ч"=>"ч", "Ш"=>"ш", "Щ"=>"щ", "Ъ"=>"ъ", "Ы"=>"ы", "Ь"=>"ь", "Э"=>"э", "Ю"=>"ю", "Я"=>"я", "Ѡ"=>"ѡ", "Ѣ"=>"ѣ", "Ѥ"=>"ѥ", "Ѧ"=>"ѧ", "Ѩ"=>"ѩ", "Ѫ"=>"ѫ", "Ѭ"=>"ѭ", "Ѯ"=>"ѯ", "Ѱ"=>"ѱ", "Ѳ"=>"ѳ", "Ѵ"=>"ѵ", "Ѷ"=>"ѷ", "Ѹ"=>"ѹ", "Ѻ"=>"ѻ", "Ѽ"=>"ѽ", "Ѿ"=>"ѿ", "Ҁ"=>"ҁ", "Ҋ"=>"ҋ", "Ҍ"=>"ҍ", "Ҏ"=>"ҏ", "Ґ"=>"ґ", "Ғ"=>"ғ", "Ҕ"=>"ҕ", "Җ"=>"җ", "Ҙ"=>"ҙ", "Қ"=>"қ", "Ҝ"=>"ҝ", "Ҟ"=>"ҟ", "Ҡ"=>"ҡ", "Ң"=>"ң", "Ҥ"=>"ҥ", "Ҧ"=>"ҧ", "Ҩ"=>"ҩ", "Ҫ"=>"ҫ", "Ҭ"=>"ҭ", "Ү"=>"ү", "Ұ"=>"ұ", "Ҳ"=>"ҳ", "Ҵ"=>"ҵ", "Ҷ"=>"ҷ", "Ҹ"=>"ҹ", "Һ"=>"һ", "Ҽ"=>"ҽ", "Ҿ"=>"ҿ", "Ӂ"=>"ӂ", "Ӄ"=>"ӄ", "Ӆ"=>"ӆ", "Ӈ"=>"ӈ", "Ӊ"=>"ӊ", "Ӌ"=>"ӌ", "Ӎ"=>"ӎ", "Ӑ"=>"ӑ", "Ӓ"=>"ӓ", "Ӕ"=>"ӕ", "Ӗ"=>"ӗ", "Ә"=>"ә", "Ӛ"=>"ӛ", "Ӝ"=>"ӝ", "Ӟ"=>"ӟ", "Ӡ"=>"ӡ", "Ӣ"=>"ӣ", "Ӥ"=>"ӥ", "Ӧ"=>"ӧ", "Ө"=>"ө", "Ӫ"=>"ӫ", "Ӭ"=>"ӭ", "Ӯ"=>"ӯ", "Ӱ"=>"ӱ", "Ӳ"=>"ӳ", "Ӵ"=>"ӵ", "Ӹ"=>"ӹ", "Ԁ"=>"ԁ", "Ԃ"=>"ԃ", "Ԅ"=>"ԅ", "Ԇ"=>"ԇ", "Ԉ"=>"ԉ", "Ԋ"=>"ԋ", "Ԍ"=>"ԍ", "Ԏ"=>"ԏ", "Ա"=>"ա", "Բ"=>"բ", "Գ"=>"գ", "Դ"=>"դ", "Ե"=>"ե", "Զ"=>"զ", "Է"=>"է", "Ը"=>"ը", "Թ"=>"թ", "Ժ"=>"ժ", "Ի"=>"ի", "Լ"=>"լ", "Խ"=>"խ", "Ծ"=>"ծ", "Կ"=>"կ", "Հ"=>"հ", "Ձ"=>"ձ", "Ղ"=>"ղ", "Ճ"=>"ճ", "Մ"=>"մ", "Յ"=>"յ", "Ն"=>"ն", "Շ"=>"շ", "Ո"=>"ո", "Չ"=>"չ", "Պ"=>"պ", "Ջ"=>"ջ", "Ռ"=>"ռ", "Ս"=>"ս", "Վ"=>"վ", "Տ"=>"տ", "Ր"=>"ր", "Ց"=>"ց", "Ւ"=>"ւ", "Փ"=>"փ", "Ք"=>"ք", "Օ"=>"օ", "Ֆ"=>"ֆ", "և"=>"եւ", "Ḁ"=>"ḁ", "Ḃ"=>"ḃ", "Ḅ"=>"ḅ", "Ḇ"=>"ḇ", "Ḉ"=>"ḉ", "Ḋ"=>"ḋ", "Ḍ"=>"ḍ", "Ḏ"=>"ḏ", "Ḑ"=>"ḑ", "Ḓ"=>"ḓ", "Ḕ"=>"ḕ", "Ḗ"=>"ḗ", "Ḙ"=>"ḙ", "Ḛ"=>"ḛ", "Ḝ"=>"ḝ", "Ḟ"=>"ḟ", "Ḡ"=>"ḡ", "Ḣ"=>"ḣ", "Ḥ"=>"ḥ", "Ḧ"=>"ḧ", "Ḩ"=>"ḩ", "Ḫ"=>"ḫ", "Ḭ"=>"ḭ", "Ḯ"=>"ḯ", "Ḱ"=>"ḱ", "Ḳ"=>"ḳ", "Ḵ"=>"ḵ", "Ḷ"=>"ḷ", "Ḹ"=>"ḹ", "Ḻ"=>"ḻ", "Ḽ"=>"ḽ", "Ḿ"=>"ḿ", "Ṁ"=>"ṁ", "Ṃ"=>"ṃ", "Ṅ"=>"ṅ", "Ṇ"=>"ṇ", "Ṉ"=>"ṉ", "Ṋ"=>"ṋ", "Ṍ"=>"ṍ", "Ṏ"=>"ṏ", "Ṑ"=>"ṑ", "Ṓ"=>"ṓ", "Ṕ"=>"ṕ", "Ṗ"=>"ṗ", "Ṙ"=>"ṙ", "Ṛ"=>"ṛ", "Ṝ"=>"ṝ", "Ṟ"=>"ṟ", "Ṡ"=>"ṡ", "Ṣ"=>"ṣ", "Ṥ"=>"ṥ", "Ṧ"=>"ṧ", "Ṩ"=>"ṩ", "Ṫ"=>"ṫ", "Ṭ"=>"ṭ", "Ṯ"=>"ṯ", "Ṱ"=>"ṱ", "Ṳ"=>"ṳ", "Ṵ"=>"ṵ", "Ṷ"=>"ṷ", "Ṹ"=>"ṹ", "Ṻ"=>"ṻ", "Ṽ"=>"ṽ", "Ṿ"=>"ṿ", "Ẁ"=>"ẁ", "Ẃ"=>"ẃ", "Ẅ"=>"ẅ", "Ẇ"=>"ẇ", "Ẉ"=>"ẉ", "Ẋ"=>"ẋ", "Ẍ"=>"ẍ", "Ẏ"=>"ẏ", "Ẑ"=>"ẑ", "Ẓ"=>"ẓ", "Ẕ"=>"ẕ", "ẖ"=>"ẖ", "ẗ"=>"ẗ", "ẘ"=>"ẘ", "ẙ"=>"ẙ", "ẚ"=>"aʾ", "ẛ"=>"ṡ", "Ạ"=>"ạ", "Ả"=>"ả", "Ấ"=>"ấ", "Ầ"=>"ầ", "Ẩ"=>"ẩ", "Ẫ"=>"ẫ", "Ậ"=>"ậ", "Ắ"=>"ắ", "Ằ"=>"ằ", "Ẳ"=>"ẳ", "Ẵ"=>"ẵ", "Ặ"=>"ặ", "Ẹ"=>"ẹ", "Ẻ"=>"ẻ", "Ẽ"=>"ẽ", "Ế"=>"ế", "Ề"=>"ề", "Ể"=>"ể", "Ễ"=>"ễ", "Ệ"=>"ệ", "Ỉ"=>"ỉ", "Ị"=>"ị", "Ọ"=>"ọ", "Ỏ"=>"ỏ", "Ố"=>"ố", "Ồ"=>"ồ", "Ổ"=>"ổ", "Ỗ"=>"ỗ", "Ộ"=>"ộ", "Ớ"=>"ớ", "Ờ"=>"ờ", "Ở"=>"ở", "Ỡ"=>"ỡ", "Ợ"=>"ợ", "Ụ"=>"ụ", "Ủ"=>"ủ", "Ứ"=>"ứ", "Ừ"=>"ừ", "Ử"=>"ử", "Ữ"=>"ữ", "Ự"=>"ự", "Ỳ"=>"ỳ", "Ỵ"=>"ỵ", "Ỷ"=>"ỷ", "Ỹ"=>"ỹ", "Ἀ"=>"ἀ", "Ἁ"=>"ἁ", "Ἂ"=>"ἂ", "Ἃ"=>"ἃ", "Ἄ"=>"ἄ", "Ἅ"=>"ἅ", "Ἆ"=>"ἆ", "Ἇ"=>"ἇ", "Ἐ"=>"ἐ", "Ἑ"=>"ἑ", "Ἒ"=>"ἒ", "Ἓ"=>"ἓ", "Ἔ"=>"ἔ", "Ἕ"=>"ἕ", "Ἠ"=>"ἠ", "Ἡ"=>"ἡ", "Ἢ"=>"ἢ", "Ἣ"=>"ἣ", "Ἤ"=>"ἤ", "Ἥ"=>"ἥ", "Ἦ"=>"ἦ", "Ἧ"=>"ἧ", "Ἰ"=>"ἰ", "Ἱ"=>"ἱ", "Ἲ"=>"ἲ", "Ἳ"=>"ἳ", "Ἴ"=>"ἴ", "Ἵ"=>"ἵ", "Ἶ"=>"ἶ", "Ἷ"=>"ἷ", "Ὀ"=>"ὀ", "Ὁ"=>"ὁ", "Ὂ"=>"ὂ", "Ὃ"=>"ὃ", "Ὄ"=>"ὄ", "Ὅ"=>"ὅ", "ὐ"=>"ὐ", "ὒ"=>"ὒ", "ὔ"=>"ὔ", "ὖ"=>"ὖ", "Ὑ"=>"ὑ", "Ὓ"=>"ὓ", "Ὕ"=>"ὕ", "Ὗ"=>"ὗ", "Ὠ"=>"ὠ", "Ὡ"=>"ὡ", "Ὢ"=>"ὢ", "Ὣ"=>"ὣ", "Ὤ"=>"ὤ", "Ὥ"=>"ὥ", "Ὦ"=>"ὦ", "Ὧ"=>"ὧ", "ᾀ"=>"ἀι", "ᾁ"=>"ἁι", "ᾂ"=>"ἂι", "ᾃ"=>"ἃι", "ᾄ"=>"ἄι", "ᾅ"=>"ἅι", "ᾆ"=>"ἆι", "ᾇ"=>"ἇι", "ᾈ"=>"ἀι", "ᾉ"=>"ἁι", "ᾊ"=>"ἂι", "ᾋ"=>"ἃι", "ᾌ"=>"ἄι", "ᾍ"=>"ἅι", "ᾎ"=>"ἆι", "ᾏ"=>"ἇι", "ᾐ"=>"ἠι", "ᾑ"=>"ἡι", "ᾒ"=>"ἢι", "ᾓ"=>"ἣι", "ᾔ"=>"ἤι", "ᾕ"=>"ἥι", "ᾖ"=>"ἦι", "ᾗ"=>"ἧι", "ᾘ"=>"ἠι", "ᾙ"=>"ἡι", "ᾚ"=>"ἢι", "ᾛ"=>"ἣι", "ᾜ"=>"ἤι", "ᾝ"=>"ἥι", "ᾞ"=>"ἦι", "ᾟ"=>"ἧι", "ᾠ"=>"ὠι", "ᾡ"=>"ὡι", "ᾢ"=>"ὢι", "ᾣ"=>"ὣι", "ᾤ"=>"ὤι", "ᾥ"=>"ὥι", "ᾦ"=>"ὦι", "ᾧ"=>"ὧι", "ᾨ"=>"ὠι", "ᾩ"=>"ὡι", "ᾪ"=>"ὢι", "ᾫ"=>"ὣι", "ᾬ"=>"ὤι", "ᾭ"=>"ὥι", "ᾮ"=>"ὦι", "ᾯ"=>"ὧι", "ᾲ"=>"ὰι", "ᾳ"=>"αι", "ᾴ"=>"άι", "ᾶ"=>"ᾶ", "ᾷ"=>"ᾶι", "Ᾰ"=>"ᾰ", "Ᾱ"=>"ᾱ", "Ὰ"=>"ὰ", "Ά"=>"ά", "ᾼ"=>"αι", "ι"=>"ι", "ῂ"=>"ὴι", "ῃ"=>"ηι", "ῄ"=>"ήι", "ῆ"=>"ῆ", "ῇ"=>"ῆι", "Ὲ"=>"ὲ", "Έ"=>"έ", "Ὴ"=>"ὴ", "Ή"=>"ή", "ῌ"=>"ηι", "ῒ"=>"ῒ", "ΐ"=>"ΐ", "ῖ"=>"ῖ", "ῗ"=>"ῗ", "Ῐ"=>"ῐ", "Ῑ"=>"ῑ", "Ὶ"=>"ὶ", "Ί"=>"ί", "ῢ"=>"ῢ", "ΰ"=>"ΰ", "ῤ"=>"ῤ", "ῦ"=>"ῦ", "ῧ"=>"ῧ", "Ῠ"=>"ῠ", "Ῡ"=>"ῡ", "Ὺ"=>"ὺ", "Ύ"=>"ύ", "Ῥ"=>"ῥ", "ῲ"=>"ὼι", "ῳ"=>"ωι", "ῴ"=>"ώι", "ῶ"=>"ῶ", "ῷ"=>"ῶι", "Ὸ"=>"ὸ", "Ό"=>"ό", "Ὼ"=>"ὼ", "Ώ"=>"ώ", "ῼ"=>"ωι", "₨"=>"rs", "ℂ"=>"c", "℃"=>"°c", "ℇ"=>"ɛ", "℉"=>"°f", "ℋ"=>"h", "ℌ"=>"h", "ℍ"=>"h", "ℐ"=>"i", "ℑ"=>"i", "ℒ"=>"l", "ℕ"=>"n", "№"=>"no", "ℙ"=>"p", "ℚ"=>"q", "ℛ"=>"r", "ℜ"=>"r", "ℝ"=>"r", "℠"=>"sm", "℡"=>"tel", "™"=>"tm", "ℤ"=>"z", "Ω"=>"ω", "ℨ"=>"z", "K"=>"k", "Å"=>"å", "ℬ"=>"b", "ℭ"=>"c", "ℰ"=>"e", "ℱ"=>"f", "ℳ"=>"m", "ℾ"=>"γ", "ℿ"=>"π", "ⅅ"=>"d", "Ⅰ"=>"ⅰ", "Ⅱ"=>"ⅱ", "Ⅲ"=>"ⅲ", "Ⅳ"=>"ⅳ", "Ⅴ"=>"ⅴ", "Ⅵ"=>"ⅵ", "Ⅶ"=>"ⅶ", "Ⅷ"=>"ⅷ", "Ⅸ"=>"ⅸ", "Ⅹ"=>"ⅹ", "Ⅺ"=>"ⅺ", "Ⅻ"=>"ⅻ", "Ⅼ"=>"ⅼ", "Ⅽ"=>"ⅽ", "Ⅾ"=>"ⅾ", "Ⅿ"=>"ⅿ", "Ⓐ"=>"ⓐ", "Ⓑ"=>"ⓑ", "Ⓒ"=>"ⓒ", "Ⓓ"=>"ⓓ", "Ⓔ"=>"ⓔ", "Ⓕ"=>"ⓕ", "Ⓖ"=>"ⓖ", "Ⓗ"=>"ⓗ", "Ⓘ"=>"ⓘ", "Ⓙ"=>"ⓙ", "Ⓚ"=>"ⓚ", "Ⓛ"=>"ⓛ", "Ⓜ"=>"ⓜ", "Ⓝ"=>"ⓝ", "Ⓞ"=>"ⓞ", "Ⓟ"=>"ⓟ", "Ⓠ"=>"ⓠ", "Ⓡ"=>"ⓡ", "Ⓢ"=>"ⓢ", "Ⓣ"=>"ⓣ", "Ⓤ"=>"ⓤ", "Ⓥ"=>"ⓥ", "Ⓦ"=>"ⓦ", "Ⓧ"=>"ⓧ", "Ⓨ"=>"ⓨ", "Ⓩ"=>"ⓩ", "㍱"=>"hpa", "㍳"=>"au", "㍵"=>"ov", "㎀"=>"pa", "㎁"=>"na", "㎂"=>"μa", "㎃"=>"ma", "㎄"=>"ka", "㎅"=>"kb", "㎆"=>"mb", "㎇"=>"gb", "㎊"=>"pf", "㎋"=>"nf", "㎌"=>"μf", "㎐"=>"hz", "㎑"=>"khz", "㎒"=>"mhz", "㎓"=>"ghz", "㎔"=>"thz", "㎩"=>"pa", "㎪"=>"kpa", "㎫"=>"mpa", "㎬"=>"gpa", "㎴"=>"pv", "㎵"=>"nv", "㎶"=>"μv", "㎷"=>"mv", "㎸"=>"kv", "㎹"=>"mv", "㎺"=>"pw", "㎻"=>"nw", "㎼"=>"μw", "㎽"=>"mw", "㎾"=>"kw", "㎿"=>"mw", "㏀"=>"kω", "㏁"=>"mω", "㏃"=>"bq", "㏆"=>"c∕kg", "㏇"=>"co.", "㏈"=>"db", "㏉"=>"gy", "㏋"=>"hp", "㏍"=>"kk", "㏎"=>"km", "㏗"=>"ph", "㏙"=>"ppm", "㏚"=>"pr", "㏜"=>"sv", "㏝"=>"wb", "ff"=>"ff", "fi"=>"fi", "fl"=>"fl", "ffi"=>"ffi", "ffl"=>"ffl", "ſt"=>"st", "st"=>"st", "ﬓ"=>"մն", "ﬔ"=>"մե", "ﬕ"=>"մի", "ﬖ"=>"վն", "ﬗ"=>"մխ", "A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e", "F"=>"f", "G"=>"g", "H"=>"h", "I"=>"i", "J"=>"j", "K"=>"k", "L"=>"l", "M"=>"m", "N"=>"n", "O"=>"o", "P"=>"p", "Q"=>"q", "R"=>"r", "S"=>"s", "T"=>"t", "U"=>"u", "V"=>"v", "W"=>"w", "X"=>"x", "Y"=>"y", "Z"=>"z", "𐐀"=>"𐐨", "𐐁"=>"𐐩", "𐐂"=>"𐐪", "𐐃"=>"𐐫", "𐐄"=>"𐐬", "𐐅"=>"𐐭", "𐐆"=>"𐐮", "𐐇"=>"𐐯", "𐐈"=>"𐐰", "𐐉"=>"𐐱", "𐐊"=>"𐐲", "𐐋"=>"𐐳", "𐐌"=>"𐐴", "𐐍"=>"𐐵", "𐐎"=>"𐐶", "𐐏"=>"𐐷", "𐐐"=>"𐐸", "𐐑"=>"𐐹", "𐐒"=>"𐐺", "𐐓"=>"𐐻", "𐐔"=>"𐐼", "𐐕"=>"𐐽", "𐐖"=>"𐐾", "𐐗"=>"𐐿", "𐐘"=>"𐑀", "𐐙"=>"𐑁", "𐐚"=>"𐑂", "𐐛"=>"𐑃", "𐐜"=>"𐑄", "𐐝"=>"𐑅", "𐐞"=>"𐑆", "𐐟"=>"𐑇", "𐐠"=>"𐑈", "𐐡"=>"𐑉", "𐐢"=>"𐑊", "𐐣"=>"𐑋", "𐐤"=>"𐑌", "𐐥"=>"𐑍", "𝐀"=>"a", "𝐁"=>"b", "𝐂"=>"c", "𝐃"=>"d", "𝐄"=>"e", "𝐅"=>"f", "𝐆"=>"g", "𝐇"=>"h", "𝐈"=>"i", "𝐉"=>"j", "𝐊"=>"k", "𝐋"=>"l", "𝐌"=>"m", "𝐍"=>"n", "𝐎"=>"o", "𝐏"=>"p", "𝐐"=>"q", "𝐑"=>"r", "𝐒"=>"s", "𝐓"=>"t", "𝐔"=>"u", "𝐕"=>"v", "𝐖"=>"w", "𝐗"=>"x", "𝐘"=>"y", "𝐙"=>"z", "𝐴"=>"a", "𝐵"=>"b", "𝐶"=>"c", "𝐷"=>"d", "𝐸"=>"e", "𝐹"=>"f", "𝐺"=>"g", "𝐻"=>"h", "𝐼"=>"i", "𝐽"=>"j", "𝐾"=>"k", "𝐿"=>"l", "𝑀"=>"m", "𝑁"=>"n", "𝑂"=>"o", "𝑃"=>"p", "𝑄"=>"q", "𝑅"=>"r", "𝑆"=>"s", "𝑇"=>"t", "𝑈"=>"u", "𝑉"=>"v", "𝑊"=>"w", "𝑋"=>"x", "𝑌"=>"y", "𝑍"=>"z", "𝑨"=>"a", "𝑩"=>"b", "𝑪"=>"c", "𝑫"=>"d", "𝑬"=>"e", "𝑭"=>"f", "𝑮"=>"g", "𝑯"=>"h", "𝑰"=>"i", "𝑱"=>"j", "𝑲"=>"k", "𝑳"=>"l", "𝑴"=>"m", "𝑵"=>"n", "𝑶"=>"o", "𝑷"=>"p", "𝑸"=>"q", "𝑹"=>"r", "𝑺"=>"s", "𝑻"=>"t", "𝑼"=>"u", "𝑽"=>"v", "𝑾"=>"w", "𝑿"=>"x", "𝒀"=>"y", "𝒁"=>"z", "𝒜"=>"a", "𝒞"=>"c", "𝒟"=>"d", "𝒢"=>"g", "𝒥"=>"j", "𝒦"=>"k", "𝒩"=>"n", "𝒪"=>"o", "𝒫"=>"p", "𝒬"=>"q", "𝒮"=>"s", "𝒯"=>"t", "𝒰"=>"u", "𝒱"=>"v", "𝒲"=>"w", "𝒳"=>"x", "𝒴"=>"y", "𝒵"=>"z", "𝓐"=>"a", "𝓑"=>"b", "𝓒"=>"c", "𝓓"=>"d", "𝓔"=>"e", "𝓕"=>"f", "𝓖"=>"g", "𝓗"=>"h", "𝓘"=>"i", "𝓙"=>"j", "𝓚"=>"k", "𝓛"=>"l", "𝓜"=>"m", "𝓝"=>"n", "𝓞"=>"o", "𝓟"=>"p", "𝓠"=>"q", "𝓡"=>"r", "𝓢"=>"s", "𝓣"=>"t", "𝓤"=>"u", "𝓥"=>"v", "𝓦"=>"w", "𝓧"=>"x", "𝓨"=>"y", "𝓩"=>"z", "𝔄"=>"a", "𝔅"=>"b", "𝔇"=>"d", "𝔈"=>"e", "𝔉"=>"f", "𝔊"=>"g", "𝔍"=>"j", "𝔎"=>"k", "𝔏"=>"l", "𝔐"=>"m", "𝔑"=>"n", "𝔒"=>"o", "𝔓"=>"p", "𝔔"=>"q", "𝔖"=>"s", "𝔗"=>"t", "𝔘"=>"u", "𝔙"=>"v", "𝔚"=>"w", "𝔛"=>"x", "𝔜"=>"y", "𝔸"=>"a", "𝔹"=>"b", "𝔻"=>"d", "𝔼"=>"e", "𝔽"=>"f", "𝔾"=>"g", "𝕀"=>"i", "𝕁"=>"j", "𝕂"=>"k", "𝕃"=>"l", "𝕄"=>"m", "𝕆"=>"o", "𝕊"=>"s", "𝕋"=>"t", "𝕌"=>"u", "𝕍"=>"v", "𝕎"=>"w", "𝕏"=>"x", "𝕐"=>"y", "𝕬"=>"a", "𝕭"=>"b", "𝕮"=>"c", "𝕯"=>"d", "𝕰"=>"e", "𝕱"=>"f", "𝕲"=>"g", "𝕳"=>"h", "𝕴"=>"i", "𝕵"=>"j", "𝕶"=>"k", "𝕷"=>"l", "𝕸"=>"m", "𝕹"=>"n", "𝕺"=>"o", "𝕻"=>"p", "𝕼"=>"q", "𝕽"=>"r", "𝕾"=>"s", "𝕿"=>"t", "𝖀"=>"u", "𝖁"=>"v", "𝖂"=>"w", "𝖃"=>"x", "𝖄"=>"y", "𝖅"=>"z", "𝖠"=>"a", "𝖡"=>"b", "𝖢"=>"c", "𝖣"=>"d", "𝖤"=>"e", "𝖥"=>"f", "𝖦"=>"g", "𝖧"=>"h", "𝖨"=>"i", "𝖩"=>"j", "𝖪"=>"k", "𝖫"=>"l", "𝖬"=>"m", "𝖭"=>"n", "𝖮"=>"o", "𝖯"=>"p", "𝖰"=>"q", "𝖱"=>"r", "𝖲"=>"s", "𝖳"=>"t", "𝖴"=>"u", "𝖵"=>"v", "𝖶"=>"w", "𝖷"=>"x", "𝖸"=>"y", "𝖹"=>"z", "𝗔"=>"a", "𝗕"=>"b", "𝗖"=>"c", "𝗗"=>"d", "𝗘"=>"e", "𝗙"=>"f", "𝗚"=>"g", "𝗛"=>"h", "𝗜"=>"i", "𝗝"=>"j", "𝗞"=>"k", "𝗟"=>"l", "𝗠"=>"m", "𝗡"=>"n", "𝗢"=>"o", "𝗣"=>"p", "𝗤"=>"q", "𝗥"=>"r", "𝗦"=>"s", "𝗧"=>"t", "𝗨"=>"u", "𝗩"=>"v", "𝗪"=>"w", "𝗫"=>"x", "𝗬"=>"y", "𝗭"=>"z", "𝘈"=>"a", "𝘉"=>"b", "𝘊"=>"c", "𝘋"=>"d", "𝘌"=>"e", "𝘍"=>"f", "𝘎"=>"g", "𝘏"=>"h", "𝘐"=>"i", "𝘑"=>"j", "𝘒"=>"k", "𝘓"=>"l", "𝘔"=>"m", "𝘕"=>"n", "𝘖"=>"o", "𝘗"=>"p", "𝘘"=>"q", "𝘙"=>"r", "𝘚"=>"s", "𝘛"=>"t", "𝘜"=>"u", "𝘝"=>"v", "𝘞"=>"w", "𝘟"=>"x", "𝘠"=>"y", "𝘡"=>"z", "𝘼"=>"a", "𝘽"=>"b", "𝘾"=>"c", "𝘿"=>"d", "𝙀"=>"e", "𝙁"=>"f", "𝙂"=>"g", "𝙃"=>"h", "𝙄"=>"i", "𝙅"=>"j", "𝙆"=>"k", "𝙇"=>"l", "𝙈"=>"m", "𝙉"=>"n", "𝙊"=>"o", "𝙋"=>"p", "𝙌"=>"q", "𝙍"=>"r", "𝙎"=>"s", "𝙏"=>"t", "𝙐"=>"u", "𝙑"=>"v", "𝙒"=>"w", "𝙓"=>"x", "𝙔"=>"y", "𝙕"=>"z", "𝙰"=>"a", "𝙱"=>"b", "𝙲"=>"c", "𝙳"=>"d", "𝙴"=>"e", "𝙵"=>"f", "𝙶"=>"g", "𝙷"=>"h", "𝙸"=>"i", "𝙹"=>"j", "𝙺"=>"k", "𝙻"=>"l", "𝙼"=>"m", "𝙽"=>"n", "𝙾"=>"o", "𝙿"=>"p", "𝚀"=>"q", "𝚁"=>"r", "𝚂"=>"s", "𝚃"=>"t", "𝚄"=>"u", "𝚅"=>"v", "𝚆"=>"w", "𝚇"=>"x", "𝚈"=>"y", "𝚉"=>"z", "𝚨"=>"α", "𝚩"=>"β", "𝚪"=>"γ", "𝚫"=>"δ", "𝚬"=>"ε", "𝚭"=>"ζ", "𝚮"=>"η", "𝚯"=>"θ", "𝚰"=>"ι", "𝚱"=>"κ", "𝚲"=>"λ", "𝚳"=>"μ", "𝚴"=>"ν", "𝚵"=>"ξ", "𝚶"=>"ο", "𝚷"=>"π", "𝚸"=>"ρ", "𝚹"=>"θ", "𝚺"=>"σ", "𝚻"=>"τ", "𝚼"=>"υ", "𝚽"=>"φ", "𝚾"=>"χ", "𝚿"=>"ψ", "𝛀"=>"ω", "𝛓"=>"σ", "𝛢"=>"α", "𝛣"=>"β", "𝛤"=>"γ", "𝛥"=>"δ", "𝛦"=>"ε", "𝛧"=>"ζ", "𝛨"=>"η", "𝛩"=>"θ", "𝛪"=>"ι", "𝛫"=>"κ", "𝛬"=>"λ", "𝛭"=>"μ", "𝛮"=>"ν", "𝛯"=>"ξ", "𝛰"=>"ο", "𝛱"=>"π", "𝛲"=>"ρ", "𝛳"=>"θ", "𝛴"=>"σ", "𝛵"=>"τ", "𝛶"=>"υ", "𝛷"=>"φ", "𝛸"=>"χ", "𝛹"=>"ψ", "𝛺"=>"ω", "𝜍"=>"σ", "𝜜"=>"α", "𝜝"=>"β", "𝜞"=>"γ", "𝜟"=>"δ", "𝜠"=>"ε", "𝜡"=>"ζ", "𝜢"=>"η", "𝜣"=>"θ", "𝜤"=>"ι", "𝜥"=>"κ", "𝜦"=>"λ", "𝜧"=>"μ", "𝜨"=>"ν", "𝜩"=>"ξ", "𝜪"=>"ο", "𝜫"=>"π", "𝜬"=>"ρ", "𝜭"=>"θ", "𝜮"=>"σ", "𝜯"=>"τ", "𝜰"=>"υ", "𝜱"=>"φ", "𝜲"=>"χ", "𝜳"=>"ψ", "𝜴"=>"ω", "𝝇"=>"σ", "𝝖"=>"α", "𝝗"=>"β", "𝝘"=>"γ", "𝝙"=>"δ", "𝝚"=>"ε", "𝝛"=>"ζ", "𝝜"=>"η", "𝝝"=>"θ", "𝝞"=>"ι", "𝝟"=>"κ", "𝝠"=>"λ", "𝝡"=>"μ", "𝝢"=>"ν", "𝝣"=>"ξ", "𝝤"=>"ο", "𝝥"=>"π", "𝝦"=>"ρ", "𝝧"=>"θ", "𝝨"=>"σ", "𝝩"=>"τ", "𝝪"=>"υ", "𝝫"=>"φ", "𝝬"=>"χ", "𝝭"=>"ψ", "𝝮"=>"ω", "𝞁"=>"σ", "𝞐"=>"α", "𝞑"=>"β", "𝞒"=>"γ", "𝞓"=>"δ", "𝞔"=>"ε", "𝞕"=>"ζ", "𝞖"=>"η", "𝞗"=>"θ", "𝞘"=>"ι", "𝞙"=>"κ", "𝞚"=>"λ", "𝞛"=>"μ", "𝞜"=>"ν", "𝞝"=>"ξ", "𝞞"=>"ο", "𝞟"=>"π", "𝞠"=>"ρ", "𝞡"=>"θ", "𝞢"=>"σ", "𝞣"=>"τ", "𝞤"=>"υ", "𝞥"=>"φ", "𝞦"=>"χ", "𝞧"=>"ψ", "𝞨"=>"ω", "𝞻"=>"σ"}.freeze + + # Replacements for IN_B.3 + MAP_B_3 = {"A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e", "F"=>"f", "G"=>"g", "H"=>"h", "I"=>"i", "J"=>"j", "K"=>"k", "L"=>"l", "M"=>"m", "N"=>"n", "O"=>"o", "P"=>"p", "Q"=>"q", "R"=>"r", "S"=>"s", "T"=>"t", "U"=>"u", "V"=>"v", "W"=>"w", "X"=>"x", "Y"=>"y", "Z"=>"z", "µ"=>"μ", "À"=>"à", "Á"=>"á", "Â"=>"â", "Ã"=>"ã", "Ä"=>"ä", "Å"=>"å", "Æ"=>"æ", "Ç"=>"ç", "È"=>"è", "É"=>"é", "Ê"=>"ê", "Ë"=>"ë", "Ì"=>"ì", "Í"=>"í", "Î"=>"î", "Ï"=>"ï", "Ð"=>"ð", "Ñ"=>"ñ", "Ò"=>"ò", "Ó"=>"ó", "Ô"=>"ô", "Õ"=>"õ", "Ö"=>"ö", "Ø"=>"ø", "Ù"=>"ù", "Ú"=>"ú", "Û"=>"û", "Ü"=>"ü", "Ý"=>"ý", "Þ"=>"þ", "ß"=>"ss", "Ā"=>"ā", "Ă"=>"ă", "Ą"=>"ą", "Ć"=>"ć", "Ĉ"=>"ĉ", "Ċ"=>"ċ", "Č"=>"č", "Ď"=>"ď", "Đ"=>"đ", "Ē"=>"ē", "Ĕ"=>"ĕ", "Ė"=>"ė", "Ę"=>"ę", "Ě"=>"ě", "Ĝ"=>"ĝ", "Ğ"=>"ğ", "Ġ"=>"ġ", "Ģ"=>"ģ", "Ĥ"=>"ĥ", "Ħ"=>"ħ", "Ĩ"=>"ĩ", "Ī"=>"ī", "Ĭ"=>"ĭ", "Į"=>"į", "İ"=>"i̇", "IJ"=>"ij", "Ĵ"=>"ĵ", "Ķ"=>"ķ", "Ĺ"=>"ĺ", "Ļ"=>"ļ", "Ľ"=>"ľ", "Ŀ"=>"ŀ", "Ł"=>"ł", "Ń"=>"ń", "Ņ"=>"ņ", "Ň"=>"ň", "ʼn"=>"ʼn", "Ŋ"=>"ŋ", "Ō"=>"ō", "Ŏ"=>"ŏ", "Ő"=>"ő", "Œ"=>"œ", "Ŕ"=>"ŕ", "Ŗ"=>"ŗ", "Ř"=>"ř", "Ś"=>"ś", "Ŝ"=>"ŝ", "Ş"=>"ş", "Š"=>"š", "Ţ"=>"ţ", "Ť"=>"ť", "Ŧ"=>"ŧ", "Ũ"=>"ũ", "Ū"=>"ū", "Ŭ"=>"ŭ", "Ů"=>"ů", "Ű"=>"ű", "Ų"=>"ų", "Ŵ"=>"ŵ", "Ŷ"=>"ŷ", "Ÿ"=>"ÿ", "Ź"=>"ź", "Ż"=>"ż", "Ž"=>"ž", "ſ"=>"s", "Ɓ"=>"ɓ", "Ƃ"=>"ƃ", "Ƅ"=>"ƅ", "Ɔ"=>"ɔ", "Ƈ"=>"ƈ", "Ɖ"=>"ɖ", "Ɗ"=>"ɗ", "Ƌ"=>"ƌ", "Ǝ"=>"ǝ", "Ə"=>"ə", "Ɛ"=>"ɛ", "Ƒ"=>"ƒ", "Ɠ"=>"ɠ", "Ɣ"=>"ɣ", "Ɩ"=>"ɩ", "Ɨ"=>"ɨ", "Ƙ"=>"ƙ", "Ɯ"=>"ɯ", "Ɲ"=>"ɲ", "Ɵ"=>"ɵ", "Ơ"=>"ơ", "Ƣ"=>"ƣ", "Ƥ"=>"ƥ", "Ʀ"=>"ʀ", "Ƨ"=>"ƨ", "Ʃ"=>"ʃ", "Ƭ"=>"ƭ", "Ʈ"=>"ʈ", "Ư"=>"ư", "Ʊ"=>"ʊ", "Ʋ"=>"ʋ", "Ƴ"=>"ƴ", "Ƶ"=>"ƶ", "Ʒ"=>"ʒ", "Ƹ"=>"ƹ", "Ƽ"=>"ƽ", "DŽ"=>"dž", "Dž"=>"dž", "LJ"=>"lj", "Lj"=>"lj", "NJ"=>"nj", "Nj"=>"nj", "Ǎ"=>"ǎ", "Ǐ"=>"ǐ", "Ǒ"=>"ǒ", "Ǔ"=>"ǔ", "Ǖ"=>"ǖ", "Ǘ"=>"ǘ", "Ǚ"=>"ǚ", "Ǜ"=>"ǜ", "Ǟ"=>"ǟ", "Ǡ"=>"ǡ", "Ǣ"=>"ǣ", "Ǥ"=>"ǥ", "Ǧ"=>"ǧ", "Ǩ"=>"ǩ", "Ǫ"=>"ǫ", "Ǭ"=>"ǭ", "Ǯ"=>"ǯ", "ǰ"=>"ǰ", "DZ"=>"dz", "Dz"=>"dz", "Ǵ"=>"ǵ", "Ƕ"=>"ƕ", "Ƿ"=>"ƿ", "Ǹ"=>"ǹ", "Ǻ"=>"ǻ", "Ǽ"=>"ǽ", "Ǿ"=>"ǿ", "Ȁ"=>"ȁ", "Ȃ"=>"ȃ", "Ȅ"=>"ȅ", "Ȇ"=>"ȇ", "Ȉ"=>"ȉ", "Ȋ"=>"ȋ", "Ȍ"=>"ȍ", "Ȏ"=>"ȏ", "Ȑ"=>"ȑ", "Ȓ"=>"ȓ", "Ȕ"=>"ȕ", "Ȗ"=>"ȗ", "Ș"=>"ș", "Ț"=>"ț", "Ȝ"=>"ȝ", "Ȟ"=>"ȟ", "Ƞ"=>"ƞ", "Ȣ"=>"ȣ", "Ȥ"=>"ȥ", "Ȧ"=>"ȧ", "Ȩ"=>"ȩ", "Ȫ"=>"ȫ", "Ȭ"=>"ȭ", "Ȯ"=>"ȯ", "Ȱ"=>"ȱ", "Ȳ"=>"ȳ", "ͅ"=>"ι", "Ά"=>"ά", "Έ"=>"έ", "Ή"=>"ή", "Ί"=>"ί", "Ό"=>"ό", "Ύ"=>"ύ", "Ώ"=>"ώ", "ΐ"=>"ΐ", "Α"=>"α", "Β"=>"β", "Γ"=>"γ", "Δ"=>"δ", "Ε"=>"ε", "Ζ"=>"ζ", "Η"=>"η", "Θ"=>"θ", "Ι"=>"ι", "Κ"=>"κ", "Λ"=>"λ", "Μ"=>"μ", "Ν"=>"ν", "Ξ"=>"ξ", "Ο"=>"ο", "Π"=>"π", "Ρ"=>"ρ", "Σ"=>"σ", "Τ"=>"τ", "Υ"=>"υ", "Φ"=>"φ", "Χ"=>"χ", "Ψ"=>"ψ", "Ω"=>"ω", "Ϊ"=>"ϊ", "Ϋ"=>"ϋ", "ΰ"=>"ΰ", "ς"=>"σ", "ϐ"=>"β", "ϑ"=>"θ", "ϕ"=>"φ", "ϖ"=>"π", "Ϙ"=>"ϙ", "Ϛ"=>"ϛ", "Ϝ"=>"ϝ", "Ϟ"=>"ϟ", "Ϡ"=>"ϡ", "Ϣ"=>"ϣ", "Ϥ"=>"ϥ", "Ϧ"=>"ϧ", "Ϩ"=>"ϩ", "Ϫ"=>"ϫ", "Ϭ"=>"ϭ", "Ϯ"=>"ϯ", "ϰ"=>"κ", "ϱ"=>"ρ", "ϲ"=>"σ", "ϴ"=>"θ", "ϵ"=>"ε", "Ѐ"=>"ѐ", "Ё"=>"ё", "Ђ"=>"ђ", "Ѓ"=>"ѓ", "Є"=>"є", "Ѕ"=>"ѕ", "І"=>"і", "Ї"=>"ї", "Ј"=>"ј", "Љ"=>"љ", "Њ"=>"њ", "Ћ"=>"ћ", "Ќ"=>"ќ", "Ѝ"=>"ѝ", "Ў"=>"ў", "Џ"=>"џ", "А"=>"а", "Б"=>"б", "В"=>"в", "Г"=>"г", "Д"=>"д", "Е"=>"е", "Ж"=>"ж", "З"=>"з", "И"=>"и", "Й"=>"й", "К"=>"к", "Л"=>"л", "М"=>"м", "Н"=>"н", "О"=>"о", "П"=>"п", "Р"=>"р", "С"=>"с", "Т"=>"т", "У"=>"у", "Ф"=>"ф", "Х"=>"х", "Ц"=>"ц", "Ч"=>"ч", "Ш"=>"ш", "Щ"=>"щ", "Ъ"=>"ъ", "Ы"=>"ы", "Ь"=>"ь", "Э"=>"э", "Ю"=>"ю", "Я"=>"я", "Ѡ"=>"ѡ", "Ѣ"=>"ѣ", "Ѥ"=>"ѥ", "Ѧ"=>"ѧ", "Ѩ"=>"ѩ", "Ѫ"=>"ѫ", "Ѭ"=>"ѭ", "Ѯ"=>"ѯ", "Ѱ"=>"ѱ", "Ѳ"=>"ѳ", "Ѵ"=>"ѵ", "Ѷ"=>"ѷ", "Ѹ"=>"ѹ", "Ѻ"=>"ѻ", "Ѽ"=>"ѽ", "Ѿ"=>"ѿ", "Ҁ"=>"ҁ", "Ҋ"=>"ҋ", "Ҍ"=>"ҍ", "Ҏ"=>"ҏ", "Ґ"=>"ґ", "Ғ"=>"ғ", "Ҕ"=>"ҕ", "Җ"=>"җ", "Ҙ"=>"ҙ", "Қ"=>"қ", "Ҝ"=>"ҝ", "Ҟ"=>"ҟ", "Ҡ"=>"ҡ", "Ң"=>"ң", "Ҥ"=>"ҥ", "Ҧ"=>"ҧ", "Ҩ"=>"ҩ", "Ҫ"=>"ҫ", "Ҭ"=>"ҭ", "Ү"=>"ү", "Ұ"=>"ұ", "Ҳ"=>"ҳ", "Ҵ"=>"ҵ", "Ҷ"=>"ҷ", "Ҹ"=>"ҹ", "Һ"=>"һ", "Ҽ"=>"ҽ", "Ҿ"=>"ҿ", "Ӂ"=>"ӂ", "Ӄ"=>"ӄ", "Ӆ"=>"ӆ", "Ӈ"=>"ӈ", "Ӊ"=>"ӊ", "Ӌ"=>"ӌ", "Ӎ"=>"ӎ", "Ӑ"=>"ӑ", "Ӓ"=>"ӓ", "Ӕ"=>"ӕ", "Ӗ"=>"ӗ", "Ә"=>"ә", "Ӛ"=>"ӛ", "Ӝ"=>"ӝ", "Ӟ"=>"ӟ", "Ӡ"=>"ӡ", "Ӣ"=>"ӣ", "Ӥ"=>"ӥ", "Ӧ"=>"ӧ", "Ө"=>"ө", "Ӫ"=>"ӫ", "Ӭ"=>"ӭ", "Ӯ"=>"ӯ", "Ӱ"=>"ӱ", "Ӳ"=>"ӳ", "Ӵ"=>"ӵ", "Ӹ"=>"ӹ", "Ԁ"=>"ԁ", "Ԃ"=>"ԃ", "Ԅ"=>"ԅ", "Ԇ"=>"ԇ", "Ԉ"=>"ԉ", "Ԋ"=>"ԋ", "Ԍ"=>"ԍ", "Ԏ"=>"ԏ", "Ա"=>"ա", "Բ"=>"բ", "Գ"=>"գ", "Դ"=>"դ", "Ե"=>"ե", "Զ"=>"զ", "Է"=>"է", "Ը"=>"ը", "Թ"=>"թ", "Ժ"=>"ժ", "Ի"=>"ի", "Լ"=>"լ", "Խ"=>"խ", "Ծ"=>"ծ", "Կ"=>"կ", "Հ"=>"հ", "Ձ"=>"ձ", "Ղ"=>"ղ", "Ճ"=>"ճ", "Մ"=>"մ", "Յ"=>"յ", "Ն"=>"ն", "Շ"=>"շ", "Ո"=>"ո", "Չ"=>"չ", "Պ"=>"պ", "Ջ"=>"ջ", "Ռ"=>"ռ", "Ս"=>"ս", "Վ"=>"վ", "Տ"=>"տ", "Ր"=>"ր", "Ց"=>"ց", "Ւ"=>"ւ", "Փ"=>"փ", "Ք"=>"ք", "Օ"=>"օ", "Ֆ"=>"ֆ", "և"=>"եւ", "Ḁ"=>"ḁ", "Ḃ"=>"ḃ", "Ḅ"=>"ḅ", "Ḇ"=>"ḇ", "Ḉ"=>"ḉ", "Ḋ"=>"ḋ", "Ḍ"=>"ḍ", "Ḏ"=>"ḏ", "Ḑ"=>"ḑ", "Ḓ"=>"ḓ", "Ḕ"=>"ḕ", "Ḗ"=>"ḗ", "Ḙ"=>"ḙ", "Ḛ"=>"ḛ", "Ḝ"=>"ḝ", "Ḟ"=>"ḟ", "Ḡ"=>"ḡ", "Ḣ"=>"ḣ", "Ḥ"=>"ḥ", "Ḧ"=>"ḧ", "Ḩ"=>"ḩ", "Ḫ"=>"ḫ", "Ḭ"=>"ḭ", "Ḯ"=>"ḯ", "Ḱ"=>"ḱ", "Ḳ"=>"ḳ", "Ḵ"=>"ḵ", "Ḷ"=>"ḷ", "Ḹ"=>"ḹ", "Ḻ"=>"ḻ", "Ḽ"=>"ḽ", "Ḿ"=>"ḿ", "Ṁ"=>"ṁ", "Ṃ"=>"ṃ", "Ṅ"=>"ṅ", "Ṇ"=>"ṇ", "Ṉ"=>"ṉ", "Ṋ"=>"ṋ", "Ṍ"=>"ṍ", "Ṏ"=>"ṏ", "Ṑ"=>"ṑ", "Ṓ"=>"ṓ", "Ṕ"=>"ṕ", "Ṗ"=>"ṗ", "Ṙ"=>"ṙ", "Ṛ"=>"ṛ", "Ṝ"=>"ṝ", "Ṟ"=>"ṟ", "Ṡ"=>"ṡ", "Ṣ"=>"ṣ", "Ṥ"=>"ṥ", "Ṧ"=>"ṧ", "Ṩ"=>"ṩ", "Ṫ"=>"ṫ", "Ṭ"=>"ṭ", "Ṯ"=>"ṯ", "Ṱ"=>"ṱ", "Ṳ"=>"ṳ", "Ṵ"=>"ṵ", "Ṷ"=>"ṷ", "Ṹ"=>"ṹ", "Ṻ"=>"ṻ", "Ṽ"=>"ṽ", "Ṿ"=>"ṿ", "Ẁ"=>"ẁ", "Ẃ"=>"ẃ", "Ẅ"=>"ẅ", "Ẇ"=>"ẇ", "Ẉ"=>"ẉ", "Ẋ"=>"ẋ", "Ẍ"=>"ẍ", "Ẏ"=>"ẏ", "Ẑ"=>"ẑ", "Ẓ"=>"ẓ", "Ẕ"=>"ẕ", "ẖ"=>"ẖ", "ẗ"=>"ẗ", "ẘ"=>"ẘ", "ẙ"=>"ẙ", "ẚ"=>"aʾ", "ẛ"=>"ṡ", "Ạ"=>"ạ", "Ả"=>"ả", "Ấ"=>"ấ", "Ầ"=>"ầ", "Ẩ"=>"ẩ", "Ẫ"=>"ẫ", "Ậ"=>"ậ", "Ắ"=>"ắ", "Ằ"=>"ằ", "Ẳ"=>"ẳ", "Ẵ"=>"ẵ", "Ặ"=>"ặ", "Ẹ"=>"ẹ", "Ẻ"=>"ẻ", "Ẽ"=>"ẽ", "Ế"=>"ế", "Ề"=>"ề", "Ể"=>"ể", "Ễ"=>"ễ", "Ệ"=>"ệ", "Ỉ"=>"ỉ", "Ị"=>"ị", "Ọ"=>"ọ", "Ỏ"=>"ỏ", "Ố"=>"ố", "Ồ"=>"ồ", "Ổ"=>"ổ", "Ỗ"=>"ỗ", "Ộ"=>"ộ", "Ớ"=>"ớ", "Ờ"=>"ờ", "Ở"=>"ở", "Ỡ"=>"ỡ", "Ợ"=>"ợ", "Ụ"=>"ụ", "Ủ"=>"ủ", "Ứ"=>"ứ", "Ừ"=>"ừ", "Ử"=>"ử", "Ữ"=>"ữ", "Ự"=>"ự", "Ỳ"=>"ỳ", "Ỵ"=>"ỵ", "Ỷ"=>"ỷ", "Ỹ"=>"ỹ", "Ἀ"=>"ἀ", "Ἁ"=>"ἁ", "Ἂ"=>"ἂ", "Ἃ"=>"ἃ", "Ἄ"=>"ἄ", "Ἅ"=>"ἅ", "Ἆ"=>"ἆ", "Ἇ"=>"ἇ", "Ἐ"=>"ἐ", "Ἑ"=>"ἑ", "Ἒ"=>"ἒ", "Ἓ"=>"ἓ", "Ἔ"=>"ἔ", "Ἕ"=>"ἕ", "Ἠ"=>"ἠ", "Ἡ"=>"ἡ", "Ἢ"=>"ἢ", "Ἣ"=>"ἣ", "Ἤ"=>"ἤ", "Ἥ"=>"ἥ", "Ἦ"=>"ἦ", "Ἧ"=>"ἧ", "Ἰ"=>"ἰ", "Ἱ"=>"ἱ", "Ἲ"=>"ἲ", "Ἳ"=>"ἳ", "Ἴ"=>"ἴ", "Ἵ"=>"ἵ", "Ἶ"=>"ἶ", "Ἷ"=>"ἷ", "Ὀ"=>"ὀ", "Ὁ"=>"ὁ", "Ὂ"=>"ὂ", "Ὃ"=>"ὃ", "Ὄ"=>"ὄ", "Ὅ"=>"ὅ", "ὐ"=>"ὐ", "ὒ"=>"ὒ", "ὔ"=>"ὔ", "ὖ"=>"ὖ", "Ὑ"=>"ὑ", "Ὓ"=>"ὓ", "Ὕ"=>"ὕ", "Ὗ"=>"ὗ", "Ὠ"=>"ὠ", "Ὡ"=>"ὡ", "Ὢ"=>"ὢ", "Ὣ"=>"ὣ", "Ὤ"=>"ὤ", "Ὥ"=>"ὥ", "Ὦ"=>"ὦ", "Ὧ"=>"ὧ", "ᾀ"=>"ἀι", "ᾁ"=>"ἁι", "ᾂ"=>"ἂι", "ᾃ"=>"ἃι", "ᾄ"=>"ἄι", "ᾅ"=>"ἅι", "ᾆ"=>"ἆι", "ᾇ"=>"ἇι", "ᾈ"=>"ἀι", "ᾉ"=>"ἁι", "ᾊ"=>"ἂι", "ᾋ"=>"ἃι", "ᾌ"=>"ἄι", "ᾍ"=>"ἅι", "ᾎ"=>"ἆι", "ᾏ"=>"ἇι", "ᾐ"=>"ἠι", "ᾑ"=>"ἡι", "ᾒ"=>"ἢι", "ᾓ"=>"ἣι", "ᾔ"=>"ἤι", "ᾕ"=>"ἥι", "ᾖ"=>"ἦι", "ᾗ"=>"ἧι", "ᾘ"=>"ἠι", "ᾙ"=>"ἡι", "ᾚ"=>"ἢι", "ᾛ"=>"ἣι", "ᾜ"=>"ἤι", "ᾝ"=>"ἥι", "ᾞ"=>"ἦι", "ᾟ"=>"ἧι", "ᾠ"=>"ὠι", "ᾡ"=>"ὡι", "ᾢ"=>"ὢι", "ᾣ"=>"ὣι", "ᾤ"=>"ὤι", "ᾥ"=>"ὥι", "ᾦ"=>"ὦι", "ᾧ"=>"ὧι", "ᾨ"=>"ὠι", "ᾩ"=>"ὡι", "ᾪ"=>"ὢι", "ᾫ"=>"ὣι", "ᾬ"=>"ὤι", "ᾭ"=>"ὥι", "ᾮ"=>"ὦι", "ᾯ"=>"ὧι", "ᾲ"=>"ὰι", "ᾳ"=>"αι", "ᾴ"=>"άι", "ᾶ"=>"ᾶ", "ᾷ"=>"ᾶι", "Ᾰ"=>"ᾰ", "Ᾱ"=>"ᾱ", "Ὰ"=>"ὰ", "Ά"=>"ά", "ᾼ"=>"αι", "ι"=>"ι", "ῂ"=>"ὴι", "ῃ"=>"ηι", "ῄ"=>"ήι", "ῆ"=>"ῆ", "ῇ"=>"ῆι", "Ὲ"=>"ὲ", "Έ"=>"έ", "Ὴ"=>"ὴ", "Ή"=>"ή", "ῌ"=>"ηι", "ῒ"=>"ῒ", "ΐ"=>"ΐ", "ῖ"=>"ῖ", "ῗ"=>"ῗ", "Ῐ"=>"ῐ", "Ῑ"=>"ῑ", "Ὶ"=>"ὶ", "Ί"=>"ί", "ῢ"=>"ῢ", "ΰ"=>"ΰ", "ῤ"=>"ῤ", "ῦ"=>"ῦ", "ῧ"=>"ῧ", "Ῠ"=>"ῠ", "Ῡ"=>"ῡ", "Ὺ"=>"ὺ", "Ύ"=>"ύ", "Ῥ"=>"ῥ", "ῲ"=>"ὼι", "ῳ"=>"ωι", "ῴ"=>"ώι", "ῶ"=>"ῶ", "ῷ"=>"ῶι", "Ὸ"=>"ὸ", "Ό"=>"ό", "Ὼ"=>"ὼ", "Ώ"=>"ώ", "ῼ"=>"ωι", "Ω"=>"ω", "K"=>"k", "Å"=>"å", "Ⅰ"=>"ⅰ", "Ⅱ"=>"ⅱ", "Ⅲ"=>"ⅲ", "Ⅳ"=>"ⅳ", "Ⅴ"=>"ⅴ", "Ⅵ"=>"ⅵ", "Ⅶ"=>"ⅶ", "Ⅷ"=>"ⅷ", "Ⅸ"=>"ⅸ", "Ⅹ"=>"ⅹ", "Ⅺ"=>"ⅺ", "Ⅻ"=>"ⅻ", "Ⅼ"=>"ⅼ", "Ⅽ"=>"ⅽ", "Ⅾ"=>"ⅾ", "Ⅿ"=>"ⅿ", "Ⓐ"=>"ⓐ", "Ⓑ"=>"ⓑ", "Ⓒ"=>"ⓒ", "Ⓓ"=>"ⓓ", "Ⓔ"=>"ⓔ", "Ⓕ"=>"ⓕ", "Ⓖ"=>"ⓖ", "Ⓗ"=>"ⓗ", "Ⓘ"=>"ⓘ", "Ⓙ"=>"ⓙ", "Ⓚ"=>"ⓚ", "Ⓛ"=>"ⓛ", "Ⓜ"=>"ⓜ", "Ⓝ"=>"ⓝ", "Ⓞ"=>"ⓞ", "Ⓟ"=>"ⓟ", "Ⓠ"=>"ⓠ", "Ⓡ"=>"ⓡ", "Ⓢ"=>"ⓢ", "Ⓣ"=>"ⓣ", "Ⓤ"=>"ⓤ", "Ⓥ"=>"ⓥ", "Ⓦ"=>"ⓦ", "Ⓧ"=>"ⓧ", "Ⓨ"=>"ⓨ", "Ⓩ"=>"ⓩ", "ff"=>"ff", "fi"=>"fi", "fl"=>"fl", "ffi"=>"ffi", "ffl"=>"ffl", "ſt"=>"st", "st"=>"st", "ﬓ"=>"մն", "ﬔ"=>"մե", "ﬕ"=>"մի", "ﬖ"=>"վն", "ﬗ"=>"մխ", "A"=>"a", "B"=>"b", "C"=>"c", "D"=>"d", "E"=>"e", "F"=>"f", "G"=>"g", "H"=>"h", "I"=>"i", "J"=>"j", "K"=>"k", "L"=>"l", "M"=>"m", "N"=>"n", "O"=>"o", "P"=>"p", "Q"=>"q", "R"=>"r", "S"=>"s", "T"=>"t", "U"=>"u", "V"=>"v", "W"=>"w", "X"=>"x", "Y"=>"y", "Z"=>"z", "𐐀"=>"𐐨", "𐐁"=>"𐐩", "𐐂"=>"𐐪", "𐐃"=>"𐐫", "𐐄"=>"𐐬", "𐐅"=>"𐐭", "𐐆"=>"𐐮", "𐐇"=>"𐐯", "𐐈"=>"𐐰", "𐐉"=>"𐐱", "𐐊"=>"𐐲", "𐐋"=>"𐐳", "𐐌"=>"𐐴", "𐐍"=>"𐐵", "𐐎"=>"𐐶", "𐐏"=>"𐐷", "𐐐"=>"𐐸", "𐐑"=>"𐐹", "𐐒"=>"𐐺", "𐐓"=>"𐐻", "𐐔"=>"𐐼", "𐐕"=>"𐐽", "𐐖"=>"𐐾", "𐐗"=>"𐐿", "𐐘"=>"𐑀", "𐐙"=>"𐑁", "𐐚"=>"𐑂", "𐐛"=>"𐑃", "𐐜"=>"𐑄", "𐐝"=>"𐑅", "𐐞"=>"𐑆", "𐐟"=>"𐑇", "𐐠"=>"𐑈", "𐐡"=>"𐑉", "𐐢"=>"𐑊", "𐐣"=>"𐑋", "𐐤"=>"𐑌", "𐐥"=>"𐑍"}.freeze + + # ASCII space characters \StringPrep\[\"C.1.1\"] + IN_C_1_1 = / /.freeze + + # Non-ASCII space characters \StringPrep\[\"C.1.2\"] + IN_C_1_2 = /[\u200b\p{Zs}&&[^ ]]/.freeze + + # ASCII control characters \StringPrep\[\"C.2.1\"] + IN_C_2_1 = /[\x00-\x1f\x7f]/.freeze + + # Non-ASCII control characters \StringPrep\[\"C.2.2\"] + IN_C_2_2 = /[\u{06dd 070f 180e feff}\u{0080}-\u{009f}\u{200c}-\u{200d}\u{2028}-\u{2029}\u{2060}-\u{2063}\u{206a}-\u{206f}\u{fff9}-\u{fffc}\u{1d173}-\u{1d17a}]/.freeze + + # Private use \StringPrep\[\"C.3\"] + IN_C_3 = /\p{private use}/.freeze + + # Non-character code points \StringPrep\[\"C.4\"] + IN_C_4 = /\p{noncharacter code point}/.freeze + + # Surrogate codes \StringPrep\[\"C.5\"] + IN_C_5 = /\p{surrogate}/.freeze + + # Inappropriate for plain text \StringPrep\[\"C.6\"] + IN_C_6 = /[\p{in specials}&&\p{AGE=3.2}&&\p{^NChar}]/.freeze + + # Inappropriate for canonical representation \StringPrep\[\"C.7\"] + IN_C_7 = /[\p{in ideographic description characters}&&\p{AGE=3.2}]/.freeze + + # Change display properties or are deprecated \StringPrep\[\"C.8\"] + IN_C_8 = /[\u{0340}-\u{0341}\u{200e}-\u{200f}\u{202a}-\u{202e}\u{206a}-\u{206f}]/.freeze + + # Tagging characters \StringPrep\[\"C.9\"] + IN_C_9 = /[\p{in Tags}&&\p{AGE=3.2}]/.freeze + + # Characters with bidirectional property "R" or "AL" \StringPrep\[\"D.1\"] + IN_D_1 = /[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]/.freeze + + # Used to check req3 of bidirectional checks + # Matches the negation of the D.1 table + IN_D_1_NEGATED = /[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]/.freeze + + # Characters with bidirectional property "L" \StringPrep\[\"D.2\"] + IN_D_2 = /[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]/.freeze + + BIDI_DESC_REQ2 = "A string with RandALCat characters must not contain LCat characters." + + # Bidirectional Characters [StringPrep, §6], Requirement 2 + # >>> + # If a string contains any RandALCat character, the string MUST NOT + # contain any LCat character. + BIDI_FAILS_REQ2 = /(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]))|(?m-ix:(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))/.freeze + + BIDI_DESC_REQ3 = "A string with RandALCat characters must start and end with RandALCat characters." + + # Bidirectional Characters [StringPrep, §6], Requirement 3 + # >>> + # If a string contains any RandALCat character, a RandALCat + # character MUST be the first character of the string, and a + # RandALCat character MUST be the last character of the string. + BIDI_FAILS_REQ3 = /(?m-ix:\A(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))|(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])\z)/.freeze + + # Bidirectional Characters [StringPrep, §6] + BIDI_FAILURE = /(?-mix:(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]))|(?m-ix:(?-mix:[\u{00aa 00b5 00ba 02ee 037a 0386 038c 0589 0903 0950 09b2 09d7 0a5e 0a83 0a8d 0ac9 0ad0 0ae0 0b40 0b57 0b83 0b9c 0bd7 0cbe 0cde 0d57 0dbd 0e84 0e8a 0e8d 0ea5 0ea7 0ebd 0ec6 0f36 0f38 0f7f 0f85 0fcf 102c 1031 1038 10fb 1248 1258 1288 12b0 12c0 1310 17dc 1f59 1f5b 1f5d 1fbe 200e 2071 207f 2102 2107 2115 2124 2126 2128 2395 1d4a2 1d4bb 1d546}\u{0041}-\u{005a}\u{0061}-\u{007a}\u{00c0}-\u{00d6}\u{00d8}-\u{00f6}\u{00f8}-\u{0220}\u{0222}-\u{0233}\u{0250}-\u{02ad}\u{02b0}-\u{02b8}\u{02bb}-\u{02c1}\u{02d0}-\u{02d1}\u{02e0}-\u{02e4}\u{0388}-\u{038a}\u{038e}-\u{03a1}\u{03a3}-\u{03ce}\u{03d0}-\u{03f5}\u{0400}-\u{0482}\u{048a}-\u{04ce}\u{04d0}-\u{04f5}\u{04f8}-\u{04f9}\u{0500}-\u{050f}\u{0531}-\u{0556}\u{0559}-\u{055f}\u{0561}-\u{0587}\u{0905}-\u{0939}\u{093d}-\u{0940}\u{0949}-\u{094c}\u{0958}-\u{0961}\u{0964}-\u{0970}\u{0982}-\u{0983}\u{0985}-\u{098c}\u{098f}-\u{0990}\u{0993}-\u{09a8}\u{09aa}-\u{09b0}\u{09b6}-\u{09b9}\u{09be}-\u{09c0}\u{09c7}-\u{09c8}\u{09cb}-\u{09cc}\u{09dc}-\u{09dd}\u{09df}-\u{09e1}\u{09e6}-\u{09f1}\u{09f4}-\u{09fa}\u{0a05}-\u{0a0a}\u{0a0f}-\u{0a10}\u{0a13}-\u{0a28}\u{0a2a}-\u{0a30}\u{0a32}-\u{0a33}\u{0a35}-\u{0a36}\u{0a38}-\u{0a39}\u{0a3e}-\u{0a40}\u{0a59}-\u{0a5c}\u{0a66}-\u{0a6f}\u{0a72}-\u{0a74}\u{0a85}-\u{0a8b}\u{0a8f}-\u{0a91}\u{0a93}-\u{0aa8}\u{0aaa}-\u{0ab0}\u{0ab2}-\u{0ab3}\u{0ab5}-\u{0ab9}\u{0abd}-\u{0ac0}\u{0acb}-\u{0acc}\u{0ae6}-\u{0aef}\u{0b02}-\u{0b03}\u{0b05}-\u{0b0c}\u{0b0f}-\u{0b10}\u{0b13}-\u{0b28}\u{0b2a}-\u{0b30}\u{0b32}-\u{0b33}\u{0b36}-\u{0b39}\u{0b3d}-\u{0b3e}\u{0b47}-\u{0b48}\u{0b4b}-\u{0b4c}\u{0b5c}-\u{0b5d}\u{0b5f}-\u{0b61}\u{0b66}-\u{0b70}\u{0b85}-\u{0b8a}\u{0b8e}-\u{0b90}\u{0b92}-\u{0b95}\u{0b99}-\u{0b9a}\u{0b9e}-\u{0b9f}\u{0ba3}-\u{0ba4}\u{0ba8}-\u{0baa}\u{0bae}-\u{0bb5}\u{0bb7}-\u{0bb9}\u{0bbe}-\u{0bbf}\u{0bc1}-\u{0bc2}\u{0bc6}-\u{0bc8}\u{0bca}-\u{0bcc}\u{0be7}-\u{0bf2}\u{0c01}-\u{0c03}\u{0c05}-\u{0c0c}\u{0c0e}-\u{0c10}\u{0c12}-\u{0c28}\u{0c2a}-\u{0c33}\u{0c35}-\u{0c39}\u{0c41}-\u{0c44}\u{0c60}-\u{0c61}\u{0c66}-\u{0c6f}\u{0c82}-\u{0c83}\u{0c85}-\u{0c8c}\u{0c8e}-\u{0c90}\u{0c92}-\u{0ca8}\u{0caa}-\u{0cb3}\u{0cb5}-\u{0cb9}\u{0cc0}-\u{0cc4}\u{0cc7}-\u{0cc8}\u{0cca}-\u{0ccb}\u{0cd5}-\u{0cd6}\u{0ce0}-\u{0ce1}\u{0ce6}-\u{0cef}\u{0d02}-\u{0d03}\u{0d05}-\u{0d0c}\u{0d0e}-\u{0d10}\u{0d12}-\u{0d28}\u{0d2a}-\u{0d39}\u{0d3e}-\u{0d40}\u{0d46}-\u{0d48}\u{0d4a}-\u{0d4c}\u{0d60}-\u{0d61}\u{0d66}-\u{0d6f}\u{0d82}-\u{0d83}\u{0d85}-\u{0d96}\u{0d9a}-\u{0db1}\u{0db3}-\u{0dbb}\u{0dc0}-\u{0dc6}\u{0dcf}-\u{0dd1}\u{0dd8}-\u{0ddf}\u{0df2}-\u{0df4}\u{0e01}-\u{0e30}\u{0e32}-\u{0e33}\u{0e40}-\u{0e46}\u{0e4f}-\u{0e5b}\u{0e81}-\u{0e82}\u{0e87}-\u{0e88}\u{0e94}-\u{0e97}\u{0e99}-\u{0e9f}\u{0ea1}-\u{0ea3}\u{0eaa}-\u{0eab}\u{0ead}-\u{0eb0}\u{0eb2}-\u{0eb3}\u{0ec0}-\u{0ec4}\u{0ed0}-\u{0ed9}\u{0edc}-\u{0edd}\u{0f00}-\u{0f17}\u{0f1a}-\u{0f34}\u{0f3e}-\u{0f47}\u{0f49}-\u{0f6a}\u{0f88}-\u{0f8b}\u{0fbe}-\u{0fc5}\u{0fc7}-\u{0fcc}\u{1000}-\u{1021}\u{1023}-\u{1027}\u{1029}-\u{102a}\u{1040}-\u{1057}\u{10a0}-\u{10c5}\u{10d0}-\u{10f8}\u{1100}-\u{1159}\u{115f}-\u{11a2}\u{11a8}-\u{11f9}\u{1200}-\u{1206}\u{1208}-\u{1246}\u{124a}-\u{124d}\u{1250}-\u{1256}\u{125a}-\u{125d}\u{1260}-\u{1286}\u{128a}-\u{128d}\u{1290}-\u{12ae}\u{12b2}-\u{12b5}\u{12b8}-\u{12be}\u{12c2}-\u{12c5}\u{12c8}-\u{12ce}\u{12d0}-\u{12d6}\u{12d8}-\u{12ee}\u{12f0}-\u{130e}\u{1312}-\u{1315}\u{1318}-\u{131e}\u{1320}-\u{1346}\u{1348}-\u{135a}\u{1361}-\u{137c}\u{13a0}-\u{13f4}\u{1401}-\u{1676}\u{1681}-\u{169a}\u{16a0}-\u{16f0}\u{1700}-\u{170c}\u{170e}-\u{1711}\u{1720}-\u{1731}\u{1735}-\u{1736}\u{1740}-\u{1751}\u{1760}-\u{176c}\u{176e}-\u{1770}\u{1780}-\u{17b6}\u{17be}-\u{17c5}\u{17c7}-\u{17c8}\u{17d4}-\u{17da}\u{17e0}-\u{17e9}\u{1810}-\u{1819}\u{1820}-\u{1877}\u{1880}-\u{18a8}\u{1e00}-\u{1e9b}\u{1ea0}-\u{1ef9}\u{1f00}-\u{1f15}\u{1f18}-\u{1f1d}\u{1f20}-\u{1f45}\u{1f48}-\u{1f4d}\u{1f50}-\u{1f57}\u{1f5f}-\u{1f7d}\u{1f80}-\u{1fb4}\u{1fb6}-\u{1fbc}\u{1fc2}-\u{1fc4}\u{1fc6}-\u{1fcc}\u{1fd0}-\u{1fd3}\u{1fd6}-\u{1fdb}\u{1fe0}-\u{1fec}\u{1ff2}-\u{1ff4}\u{1ff6}-\u{1ffc}\u{210a}-\u{2113}\u{2119}-\u{211d}\u{212a}-\u{212d}\u{212f}-\u{2131}\u{2133}-\u{2139}\u{213d}-\u{213f}\u{2145}-\u{2149}\u{2160}-\u{2183}\u{2336}-\u{237a}\u{249c}-\u{24e9}\u{3005}-\u{3007}\u{3021}-\u{3029}\u{3031}-\u{3035}\u{3038}-\u{303c}\u{3041}-\u{3096}\u{309d}-\u{309f}\u{30a1}-\u{30fa}\u{30fc}-\u{30ff}\u{3105}-\u{312c}\u{3131}-\u{318e}\u{3190}-\u{31b7}\u{31f0}-\u{321c}\u{3220}-\u{3243}\u{3260}-\u{327b}\u{327f}-\u{32b0}\u{32c0}-\u{32cb}\u{32d0}-\u{32fe}\u{3300}-\u{3376}\u{337b}-\u{33dd}\u{33e0}-\u{33fe}\u{3400}-\u{4db5}\u{4e00}-\u{9fa5}\u{a000}-\u{a48c}\u{ac00}-\u{d7a3}\u{e000}-\u{fa2d}\u{fa30}-\u{fa6a}\u{fb00}-\u{fb06}\u{fb13}-\u{fb17}\u{ff21}-\u{ff3a}\u{ff41}-\u{ff5a}\u{ff66}-\u{ffbe}\u{ffc2}-\u{ffc7}\u{ffca}-\u{ffcf}\u{ffd2}-\u{ffd7}\u{ffda}-\u{ffdc}\u{10300}-\u{1031e}\u{10320}-\u{10323}\u{10330}-\u{1034a}\u{10400}-\u{10425}\u{10428}-\u{1044d}\u{1d000}-\u{1d0f5}\u{1d100}-\u{1d126}\u{1d12a}-\u{1d166}\u{1d16a}-\u{1d172}\u{1d183}-\u{1d184}\u{1d18c}-\u{1d1a9}\u{1d1ae}-\u{1d1dd}\u{1d400}-\u{1d454}\u{1d456}-\u{1d49c}\u{1d49e}-\u{1d49f}\u{1d4a5}-\u{1d4a6}\u{1d4a9}-\u{1d4ac}\u{1d4ae}-\u{1d4b9}\u{1d4bd}-\u{1d4c0}\u{1d4c2}-\u{1d4c3}\u{1d4c5}-\u{1d505}\u{1d507}-\u{1d50a}\u{1d50d}-\u{1d514}\u{1d516}-\u{1d51c}\u{1d51e}-\u{1d539}\u{1d53b}-\u{1d53e}\u{1d540}-\u{1d544}\u{1d54a}-\u{1d550}\u{1d552}-\u{1d6a3}\u{1d6a8}-\u{1d7c9}\u{20000}-\u{2a6d6}\u{2f800}-\u{2fa1d}\u{f0000}-\u{ffffd}\u{100000}-\u{10fffd}\p{Cs}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])))|(?-mix:(?m-ix:\A(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]))|(?m-ix:(?-mix:[\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}]).*?(?-mix:[^\u{05be 05c0 05c3 061b 061f 06dd 0710 07b1 200f fb1d fb3e}\u{05d0}-\u{05ea}\u{05f0}-\u{05f4}\u{0621}-\u{063a}\u{0640}-\u{064a}\u{066d}-\u{066f}\u{0671}-\u{06d5}\u{06e5}-\u{06e6}\u{06fa}-\u{06fe}\u{0700}-\u{070d}\u{0712}-\u{072c}\u{0780}-\u{07a5}\u{fb1f}-\u{fb28}\u{fb2a}-\u{fb36}\u{fb38}-\u{fb3c}\u{fb40}-\u{fb41}\u{fb43}-\u{fb44}\u{fb46}-\u{fbb1}\u{fbd3}-\u{fd3d}\u{fd50}-\u{fd8f}\u{fd92}-\u{fdc7}\u{fdf0}-\u{fdfc}\u{fe70}-\u{fe74}\u{fe76}-\u{fefc}])\z))/.freeze + + # Names of each codepoint table in the RFC-3454 appendices + TITLES = { + "A.1" => "Unassigned code points in Unicode 3.2", + "B.1" => "Commonly mapped to nothing", + "B.2" => "Mapping for case-folding used with NFKC", + "B.3" => "Mapping for case-folding used with no normalization", + "C.1" => "Space characters", + "C.1.1" => "ASCII space characters", + "C.1.2" => "Non-ASCII space characters", + "C.2" => "Control characters", + "C.2.1" => "ASCII control characters", + "C.2.2" => "Non-ASCII control characters", + "C.3" => "Private use", + "C.4" => "Non-character code points", + "C.5" => "Surrogate codes", + "C.6" => "Inappropriate for plain text", + "C.7" => "Inappropriate for canonical representation", + "C.8" => "Change display properties or are deprecated", + "C.9" => "Tagging characters", + "D.1" => "Characters with bidirectional property \"R\" or \"AL\"", + "D.2" => "Characters with bidirectional property \"L\"", + }.freeze + + # Regexps matching each codepoint table in the RFC-3454 appendices + REGEXPS = { + "A.1" => IN_A_1, + "B.1" => IN_B_1, + "B.2" => IN_B_2, + "B.3" => IN_B_3, + "C.1.1" => IN_C_1_1, + "C.1.2" => IN_C_1_2, + "C.2.1" => IN_C_2_1, + "C.2.2" => IN_C_2_2, + "C.3" => IN_C_3, + "C.4" => IN_C_4, + "C.5" => IN_C_5, + "C.6" => IN_C_6, + "C.7" => IN_C_7, + "C.8" => IN_C_8, + "C.9" => IN_C_9, + "D.1" => IN_D_1, + "D.2" => IN_D_2, + }.freeze + + MAPPINGS = { + "B.1" => [IN_B_1, MAP_B_1].freeze, + "B.2" => [IN_B_2, MAP_B_2].freeze, + "B.3" => [IN_B_3, MAP_B_3].freeze, + }.freeze + + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/trace.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/trace.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/trace.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep/trace.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module Net + class IMAP + module StringPrep + + # Defined in RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The +trace+ + # profile of \StringPrep is used by the +ANONYMOUS+ \SASL mechanism. + module Trace + + # Defined in RFC-4505[https://tools.ietf.org/html/rfc4505] §3. + STRINGPREP_PROFILE = "trace" + + # >>> + # The character repertoire of this profile is Unicode 3.2 [Unicode]. + UNASSIGNED_TABLE = "A.1" + + # >>> + # No mapping is required by this profile. + MAPPING_TABLES = nil + + # >>> + # No Unicode normalization is required by this profile. + NORMALIZATION = nil + + # From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace" + # Profile of "Stringprep": + # >>> + # Characters from the following tables of [StringPrep] are prohibited: + # + # - C.2.1 (ASCII control characters) + # - C.2.2 (Non-ASCII control characters) + # - C.3 (Private use characters) + # - C.4 (Non-character code points) + # - C.5 (Surrogate codes) + # - C.6 (Inappropriate for plain text) + # - C.8 (Change display properties are deprecated) + # - C.9 (Tagging characters) + # + # No additional characters are prohibited. + PROHIBITED_TABLES = %w[C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.8 C.9].freeze + + # >>> + # This profile requires bidirectional character checking per Section 6 + # of [StringPrep]. + CHECK_BIDI = true + + module_function + + # From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace" + # Profile of "Stringprep": + # >>> + # The character repertoire of this profile is Unicode 3.2 [Unicode]. + # + # No mapping is required by this profile. + # + # No Unicode normalization is required by this profile. + # + # The list of unassigned code points for this profile is that provided + # in Appendix A of [StringPrep]. Unassigned code points are not + # prohibited. + # + # Characters from the following tables of [StringPrep] are prohibited: + # (documented on PROHIBITED_TABLES) + # + # This profile requires bidirectional character checking per Section 6 + # of [StringPrep]. + def stringprep_trace(string, **opts) + StringPrep.stringprep( + string, + unassigned: UNASSIGNED_TABLE, + maps: MAPPING_TABLES, + prohibited: PROHIBITED_TABLES, + normalization: NORMALIZATION, + bidi: CHECK_BIDI, + profile: STRINGPREP_PROFILE, + **opts, + ) + end + + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap/stringprep.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +module Net + class IMAP < Protocol + + # Regexps and utility methods for implementing stringprep profiles. The + # \StringPrep algorithm is defined by + # {RFC-3454}[https://www.rfc-editor.org/rfc/rfc3454.html]. Each + # codepoint table defined in the RFC-3454 appendices is matched by a Regexp + # defined in this module. + module StringPrep + autoload :NamePrep, File.expand_path("stringprep/nameprep", __dir__) + autoload :SASLprep, File.expand_path("stringprep/saslprep", __dir__) + autoload :Tables, File.expand_path("stringprep/tables", __dir__) + autoload :Trace, File.expand_path("stringprep/trace", __dir__) + + # ArgumentError raised when +string+ is invalid for the stringprep + # +profile+. + class StringPrepError < ArgumentError + attr_reader :string, :profile + + def initialize(*args, string: nil, profile: nil) + @string = -string.to_str unless string.nil? + @profile = -profile.to_str unless profile.nil? + super(*args) + end + end + + # StringPrepError raised when +string+ contains a codepoint prohibited by + # +table+. + class ProhibitedCodepoint < StringPrepError + attr_reader :table + + def initialize(table, *args, **kwargs) + @table = table + details = (title = Tables::TITLES[table]) ? + "%s [%s]" % [title, table] : table + message = "String contains a prohibited codepoint: %s" % [details] + super(message, *args, **kwargs) + end + end + + # StringPrepError raised when +string+ contains bidirectional characters + # which violate the StringPrep requirements. + class BidiStringError < StringPrepError + end + + # Returns a Regexp matching the given +table+ name. + def self.[](table) + Tables::REGEXPS.fetch(table) + end + + module_function + + # >>> + # 1. Map -- For each character in the input, check if it has a mapping + # and, if so, replace it with its mapping. This is described in + # section 3. + # + # 2. Normalize -- Possibly normalize the result of step 1 using Unicode + # normalization. This is described in section 4. + # + # 3. Prohibit -- Check for any characters that are not allowed in the + # output. If any are found, return an error. This is described in + # section 5. + # + # 4. Check bidi -- Possibly check for right-to-left characters, and if + # any are found, make sure that the whole string satisfies the + # requirements for bidirectional strings. If the string does not + # satisfy the requirements for bidirectional strings, return an + # error. This is described in section 6. + # + # The above steps MUST be performed in the order given to comply with + # this specification. + # + def stringprep(string, + maps:, + normalization:, + prohibited:, + **opts) + string = string.encode("UTF-8") # also dups (and raises invalid encoding) + map_tables!(string, *maps) if maps + string.unicode_normalize!(normalization) if normalization + check_prohibited!(string, *prohibited, **opts) if prohibited + string + end + + def map_tables!(string, *tables) + tables.each do |table| + regexp, replacements = Tables::MAPPINGS.fetch(table) + string.gsub!(regexp, replacements) + end + string + end + + # Checks +string+ for any codepoint in +tables+. Raises a + # ProhibitedCodepoint describing the first matching table. + # + # Also checks bidirectional characters, when bidi: true, which may + # raise a BidiStringError. + # + # +profile+ is an optional string which will be added to any exception that + # is raised (it does not affect behavior). + def check_prohibited!(string, + *tables, + bidi: false, + unassigned: "A.1", + stored: false, + profile: nil) + tables = Tables::TITLES.keys.grep(/^C/) if tables.empty? + tables |= [unassigned] if stored + tables |= %w[C.8] if bidi + table = tables.find {|t| + case t + when String then Tables::REGEXPS.fetch(t).match?(string) + when Regexp then t.match?(string) + else raise ArgumentError, "only table names and regexps can be checked" + end + } + if table + raise ProhibitedCodepoint.new( + table, string: string, profile: profile + ) + end + check_bidi!(string, profile: profile) if bidi + end + + # Checks that +string+ obeys all of the "Bidirectional Characters" + # requirements in RFC-3454, §6: + # + # * The characters in \StringPrep\[\"C.8\"] MUST be prohibited + # * If a string contains any RandALCat character, the string MUST NOT + # contain any LCat character. + # * If a string contains any RandALCat character, a RandALCat + # character MUST be the first character of the string, and a + # RandALCat character MUST be the last character of the string. + # + # This is usually combined with #check_prohibited!, so table "C.8" is only + # checked when c_8: true. + # + # Raises either ProhibitedCodepoint or BidiStringError unless all + # requirements are met. +profile+ is an optional string which will be + # added to any exception that is raised (it does not affect behavior). + def check_bidi!(string, c_8: false, profile: nil) + check_prohibited!(string, "C.8", profile: profile) if c_8 + if Tables::BIDI_FAILS_REQ2.match?(string) + raise BidiStringError.new( + Tables::BIDI_DESC_REQ2, string: string, profile: profile, + ) + elsif Tables::BIDI_FAILS_REQ3.match?(string) + raise BidiStringError.new( + Tables::BIDI_DESC_REQ3, string: string, profile: profile, + ) + end + end + + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/lib/net/imap.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/lib/net/imap.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,2970 @@ +# frozen_string_literal: true +# +# = net/imap.rb +# +# Copyright (C) 2000 Shugo Maeda +# +# This library is distributed under the terms of the Ruby license. +# You can freely distribute/modify this library. +# +# Documentation: Shugo Maeda, with RDoc conversion and overview by William +# Webber. +# +# See Net::IMAP for documentation. +# + +require "socket" +require "monitor" +require 'net/protocol' +begin + require "openssl" +rescue LoadError +end + +module Net + + # Net::IMAP implements Internet Message Access Protocol (\IMAP) client + # functionality. The protocol is described + # in {IMAP4rev1 [RFC3501]}[https://tools.ietf.org/html/rfc3501] + # and {IMAP4rev2 [RFC9051]}[https://tools.ietf.org/html/rfc9051]. + # + # == \IMAP Overview + # + # An \IMAP client connects to a server, and then authenticates + # itself using either #authenticate or #login. Having + # authenticated itself, there is a range of commands + # available to it. Most work with mailboxes, which may be + # arranged in an hierarchical namespace, and each of which + # contains zero or more messages. How this is implemented on + # the server is implementation-dependent; on a UNIX server, it + # will frequently be implemented as files in mailbox format + # within a hierarchy of directories. + # + # To work on the messages within a mailbox, the client must + # first select that mailbox, using either #select or #examine + # (for read-only access). Once the client has successfully + # selected a mailbox, they enter the "_selected_" state, and that + # mailbox becomes the _current_ mailbox, on which mail-item + # related commands implicitly operate. + # + # === Sequence numbers and UIDs + # + # Messages have two sorts of identifiers: message sequence + # numbers and UIDs. + # + # Message sequence numbers number messages within a mailbox + # from 1 up to the number of items in the mailbox. If a new + # message arrives during a session, it receives a sequence + # number equal to the new size of the mailbox. If messages + # are expunged from the mailbox, remaining messages have their + # sequence numbers "shuffled down" to fill the gaps. + # + # To avoid sequence number race conditions, servers must not expunge messages + # when no command is in progress, nor when responding to #fetch, #store, or + # #search. Expunges _may_ be sent during any other command, including + # #uid_fetch, #uid_store, and #uid_search. The #noop and #idle commands are + # both useful for this side-effect: they allow the server to send all mailbox + # updates, including expunges. + # + # UIDs, on the other hand, are permanently guaranteed not to + # identify another message within the same mailbox, even if + # the existing message is deleted. UIDs are required to + # be assigned in ascending (but not necessarily sequential) + # order within a mailbox; this means that if a non-IMAP client + # rearranges the order of mail items within a mailbox, the + # UIDs have to be reassigned. An \IMAP client thus cannot + # rearrange message orders. + # + # === Examples of Usage + # + # ==== List sender and subject of all recent messages in the default mailbox + # + # imap = Net::IMAP.new('mail.example.com') + # imap.authenticate('PLAIN', 'joe_user', 'joes_password') + # imap.examine('INBOX') + # imap.search(["RECENT"]).each do |message_id| + # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"] + # puts "#{envelope.from[0].name}: \t#{envelope.subject}" + # end + # + # ==== Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03" + # + # imap = Net::IMAP.new('mail.example.com') + # imap.authenticate('PLAIN', 'joe_user', 'joes_password') + # imap.select('Mail/sent-mail') + # if not imap.list('Mail/', 'sent-apr03') + # imap.create('Mail/sent-apr03') + # end + # imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id| + # imap.copy(message_id, "Mail/sent-apr03") + # imap.store(message_id, "+FLAGS", [:Deleted]) + # end + # imap.expunge + # + # == Capabilities + # + # Most Net::IMAP methods do not _currently_ modify their behaviour according + # to the server's advertised #capabilities. Users of this class must check + # that the server is capable of extension commands or command arguments before + # sending them. Special care should be taken to follow the #capabilities + # requirements for #starttls, #login, and #authenticate. + # + # See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover + # server capabilities. For relevant capability requirements, see the + # documentation on each \IMAP command. + # + # imap = Net::IMAP.new("mail.example.com") + # imap.capable?(:IMAP4rev1) or raise "Not an IMAP4rev1 server" + # imap.capable?(:starttls) or raise "Cannot start TLS" + # imap.starttls + # + # if imap.auth_capable?("PLAIN") + # imap.authenticate "PLAIN", username, password + # elsif !imap.capability?("LOGINDISABLED") + # imap.login username, password + # else + # raise "No acceptable authentication mechanisms" + # end + # + # # Support for "UTF8=ACCEPT" implies support for "ENABLE" + # imap.enable :utf8 if imap.capable?("UTF8=ACCEPT") + # + # namespaces = imap.namespace if imap.capable?(:namespace) + # mbox_prefix = namespaces&.personal&.first&.prefix || "" + # mbox_delim = namespaces&.personal&.first&.delim || "/" + # mbox_path = prefix + %w[path to my mailbox].join(delim) + # imap.create mbox_path + # + # === Basic IMAP4rev1 capabilities + # + # IMAP4rev1 servers must advertise +IMAP4rev1+ in their capabilities list. + # IMAP4rev1 servers must _implement_ the +STARTTLS+, AUTH=PLAIN, + # and +LOGINDISABLED+ capabilities. See #starttls, #login, and #authenticate + # for the implications of these capabilities. + # + # === Caching +CAPABILITY+ responses + # + # Net::IMAP automatically stores and discards capability data according to the + # the requirements and recommendations in + # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1], + # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and + # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1]. + # Use #capable?, #auth_capable?, or #capabilities to use this cache and avoid + # sending the #capability command unnecessarily. + # + # The server may advertise its initial capabilities using the +CAPABILITY+ + # ResponseCode in a +PREAUTH+ or +OK+ #greeting. When TLS has started + # (#starttls) and after authentication (#login or #authenticate), the server's + # capabilities may change and cached capabilities are discarded. The server + # may send updated capabilities with an +OK+ TaggedResponse to #login or + # #authenticate, and these will be cached by Net::IMAP. But the + # TaggedResponse to #starttls MUST be ignored--it is sent before TLS starts + # and is unprotected. + # + # When storing capability values to variables, be careful that they are + # discarded or reset appropriately, especially following #starttls. + # + # === Using IMAP4rev1 extensions + # + # See the {IANA IMAP4 capabilities + # registry}[http://www.iana.org/assignments/imap4-capabilities] for a list of + # all standard capabilities, and their reference RFCs. + # + # IMAP4rev1 servers must not activate behavior that is incompatible with the + # base specification until an explicit client action invokes a capability, + # e.g. sending a command or command argument specific to that capability. + # Servers may send data with backward compatible behavior, such as response + # codes or mailbox attributes, at any time without client action. + # + # Invoking capabilities which are unknown to Net::IMAP may cause unexpected + # behavior and errors. For example, ResponseParseError is raised when + # unknown response syntax is received. Invoking commands or command + # parameters that are unsupported by the server may raise NoResponseError, + # BadResponseError, or cause other unexpected behavior. + # + # Some capabilities must be explicitly activated using the #enable command. + # See #enable for details. + # + # == Thread Safety + # + # Net::IMAP supports concurrent threads. For example, + # + # imap = Net::IMAP.new("imap.foo.net", "imap2") + # imap.authenticate("scram-md5", "bar", "password") + # imap.select("inbox") + # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") } + # search_result = imap.search(["BODY", "hello"]) + # fetch_result = fetch_thread.value + # imap.disconnect + # + # This script invokes the FETCH command and the SEARCH command concurrently. + # + # == Errors + # + # An \IMAP server can send three different types of responses to indicate + # failure: + # + # NO:: the attempted command could not be successfully completed. For + # instance, the username/password used for logging in are incorrect; + # the selected mailbox does not exist; etc. + # + # BAD:: the request from the client does not follow the server's + # understanding of the \IMAP protocol. This includes attempting + # commands from the wrong client state; for instance, attempting + # to perform a SEARCH command without having SELECTed a current + # mailbox. It can also signal an internal server + # failure (such as a disk crash) has occurred. + # + # BYE:: the server is saying goodbye. This can be part of a normal + # logout sequence, and can be used as part of a login sequence + # to indicate that the server is (for some reason) unwilling + # to accept your connection. As a response to any other command, + # it indicates either that the server is shutting down, or that + # the server is timing out the client connection due to inactivity. + # + # These three error response are represented by the errors + # Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, and + # Net::IMAP::ByeResponseError, all of which are subclasses of + # Net::IMAP::ResponseError. Essentially, all methods that involve + # sending a request to the server can generate one of these errors. + # Only the most pertinent instances have been documented below. + # + # Because the IMAP class uses Sockets for communication, its methods + # are also susceptible to the various errors that can occur when + # working with sockets. These are generally represented as + # Errno errors. For instance, any method that involves sending a + # request to the server and/or receiving a response from it could + # raise an Errno::EPIPE error if the network connection unexpectedly + # goes down. See the socket(7), ip(7), tcp(7), socket(2), connect(2), + # and associated man pages. + # + # Finally, a Net::IMAP::DataFormatError is thrown if low-level data + # is found to be in an incorrect format (for instance, when converting + # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is + # thrown if a server response is non-parseable. + # + # == What's here? + # + # * {Connection control}[rdoc-ref:Net::IMAP@Connection+control+methods] + # * {Server capabilities}[rdoc-ref:Net::IMAP@Server+capabilities] + # * {Handling server responses}[rdoc-ref:Net::IMAP@Handling+server+responses] + # * {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands] + # * {for any state}[rdoc-ref:Net::IMAP@Any+state] + # * {for the "not authenticated" state}[rdoc-ref:Net::IMAP@Not+Authenticated+state] + # * {for the "authenticated" state}[rdoc-ref:Net::IMAP@Authenticated+state] + # * {for the "selected" state}[rdoc-ref:Net::IMAP@Selected+state] + # * {for the "logout" state}[rdoc-ref:Net::IMAP@Logout+state] + # * {IMAP extension support}[rdoc-ref:Net::IMAP@IMAP+extension+support] + # + # === Connection control methods + # + # - Net::IMAP.new: Creates a new \IMAP client which connects immediately and + # waits for a successful server greeting before the method returns. + # - #starttls: Asks the server to upgrade a clear-text connection to use TLS. + # - #logout: Tells the server to end the session. Enters the "_logout_" state. + # - #disconnect: Disconnects the connection (without sending #logout first). + # - #disconnected?: True if the connection has been closed. + # + # === Server capabilities + # + # - #capable?: Returns whether the server supports a given capability. + # - #capabilities: Returns the server's capabilities as an array of strings. + # - #auth_capable?: Returns whether the server advertises support for a given + # SASL mechanism, for use with #authenticate. + # - #auth_mechanisms: Returns the #authenticate SASL mechanisms which + # the server claims to support as an array of strings. + # - #clear_cached_capabilities: Clears cached capabilities. + # + # The capabilities cache is automatically cleared after completing + # #starttls, #login, or #authenticate. + # - #capability: Sends the +CAPABILITY+ command and returns the #capabilities. + # + # In general, #capable? should be used rather than explicitly sending a + # +CAPABILITY+ command to the server. + # + # === Handling server responses + # + # - #greeting: The server's initial untagged response, which can indicate a + # pre-authenticated connection. + # - #responses: Yields unhandled UntaggedResponse#data and non-+nil+ + # ResponseCode#data. + # - #clear_responses: Deletes unhandled data from #responses and returns it. + # - #add_response_handler: Add a block to be called inside the receiver thread + # with every server response. + # - #response_handlers: Returns the list of response handlers. + # - #remove_response_handler: Remove a previously added response handler. + # + # === Core \IMAP commands + # + # The following commands are defined either by + # the [IMAP4rev1[https://tools.ietf.org/html/rfc3501]] base specification, or + # by one of the following extensions: + # [IDLE[https://tools.ietf.org/html/rfc2177]], + # [NAMESPACE[https://tools.ietf.org/html/rfc2342]], + # [UNSELECT[https://tools.ietf.org/html/rfc3691]], + # [ENABLE[https://tools.ietf.org/html/rfc5161]], + # [MOVE[https://tools.ietf.org/html/rfc6851]]. + # These extensions are widely supported by modern IMAP4rev1 servers and have + # all been integrated into [IMAP4rev2[https://tools.ietf.org/html/rfc9051]]. + # *NOTE:* Net::IMAP doesn't support IMAP4rev2 yet. + # + # ==== Any state + # + # - #capability: Returns the server's capabilities as an array of strings. + # + # In general, #capable? should be used rather than explicitly sending a + # +CAPABILITY+ command to the server. + # - #noop: Allows the server to send unsolicited untagged #responses. + # - #logout: Tells the server to end the session. Enters the "_logout_" state. + # + # ==== Not Authenticated state + # + # In addition to the commands for any state, the following commands are valid + # in the "not authenticated" state: + # + # - #starttls: Upgrades a clear-text connection to use TLS. + # + # Requires the +STARTTLS+ capability. + # - #authenticate: Identifies the client to the server using the given + # {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # and credentials. Enters the "_authenticated_" state. + # + # The server should list "AUTH=#{mechanism}" capabilities for + # supported mechanisms. + # - #login: Identifies the client to the server using a plain text password. + # Using #authenticate is generally preferred. Enters the "_authenticated_" + # state. + # + # The +LOGINDISABLED+ capability must NOT be listed. + # + # ==== Authenticated state + # + # In addition to the commands for any state, the following commands are valid + # in the "_authenticated_" state: + # + # - #enable: Enables backwards incompatible server extensions. + # Requires the +ENABLE+ or +IMAP4rev2+ capability. + # - #select: Open a mailbox and enter the "_selected_" state. + # - #examine: Open a mailbox read-only, and enter the "_selected_" state. + # - #create: Creates a new mailbox. + # - #delete: Permanently remove a mailbox. + # - #rename: Change the name of a mailbox. + # - #subscribe: Adds a mailbox to the "subscribed" set. + # - #unsubscribe: Removes a mailbox from the "subscribed" set. + # - #list: Returns names and attributes of mailboxes matching a given pattern. + # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters. + # Requires the +NAMESPACE+ or +IMAP4rev2+ capability. + # - #status: Returns mailbox information, e.g. message count, unseen message + # count, +UIDVALIDITY+ and +UIDNEXT+. + # - #append: Appends a message to the end of a mailbox. + # - #idle: Allows the server to send updates to the client, without the client + # needing to poll using #noop. + # Requires the +IDLE+ or +IMAP4rev2+ capability. + # - *Obsolete* #lsub: Replaced by LIST-EXTENDED and removed from + # +IMAP4rev2+. Lists mailboxes in the "subscribed" set. + # + # *Note:* Net::IMAP hasn't implemented LIST-EXTENDED yet. + # + # ==== Selected state + # + # In addition to the commands for any state and the "_authenticated_" + # commands, the following commands are valid in the "_selected_" state: + # + # - #close: Closes the mailbox and returns to the "_authenticated_" state, + # expunging deleted messages, unless the mailbox was opened as read-only. + # - #unselect: Closes the mailbox and returns to the "_authenticated_" state, + # without expunging any messages. + # Requires the +UNSELECT+ or +IMAP4rev2+ capability. + # - #expunge: Permanently removes messages which have the Deleted flag set. + # - #uid_expunge: Restricts expunge to only remove the specified UIDs. + # Requires the +UIDPLUS+ or +IMAP4rev2+ capability. + # - #search, #uid_search: Returns sequence numbers or UIDs of messages that + # match the given searching criteria. + # - #fetch, #uid_fetch: Returns data associated with a set of messages, + # specified by sequence number or UID. + # - #store, #uid_store: Alters a message's flags. + # - #copy, #uid_copy: Copies the specified messages to the end of the + # specified destination mailbox. + # - #move, #uid_move: Moves the specified messages to the end of the + # specified destination mailbox, expunging them from the current mailbox. + # Requires the +MOVE+ or +IMAP4rev2+ capability. + # - #check: *Obsolete:* removed from +IMAP4rev2+. + # Can be replaced with #noop or #idle. + # + # ==== Logout state + # + # No \IMAP commands are valid in the "_logout_" state. If the socket is still + # open, Net::IMAP will close it after receiving server confirmation. + # Exceptions will be raised by \IMAP commands that have already started and + # are waiting for a response, as well as any that are called after logout. + # + # === \IMAP extension support + # + # ==== RFC9051: +IMAP4rev2+ + # + # Although IMAP4rev2[https://tools.ietf.org/html/rfc9051] is not supported + # yet, Net::IMAP supports several extensions that have been folded into it: + # +ENABLE+, +IDLE+, +MOVE+, +NAMESPACE+, +SASL-IR+, +UIDPLUS+, +UNSELECT+, + # STATUS=SIZE, and the fetch side of +BINARY+. + # Commands for these extensions are listed with the {Core IMAP + # commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands], above. + # + # >>> + # The following are folded into +IMAP4rev2+ but are currently + # unsupported or incompletely supported by Net::IMAP: RFC4466 + # extensions, +ESEARCH+, +SEARCHRES+, +LIST-EXTENDED+, +LIST-STATUS+, + # +LITERAL-+, and +SPECIAL-USE+. + # + # ==== RFC2087: +QUOTA+ + # - #getquota: returns the resource usage and limits for a quota root + # - #getquotaroot: returns the list of quota roots for a mailbox, as well as + # their resource usage and limits. + # - #setquota: sets the resource limits for a given quota root. + # + # ==== RFC2177: +IDLE+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included + # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #idle: Allows the server to send updates to the client, without the client + # needing to poll using #noop. + # + # ==== RFC2342: +NAMESPACE+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included + # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #namespace: Returns mailbox namespaces, with path prefixes and delimiters. + # + # ==== RFC2971: +ID+ + # - #id: exchanges client and server implementation information. + # + # ==== RFC3516: +BINARY+ + # The fetch side of +BINARY+ has been folded into + # IMAP4rev2[https://tools.ietf.org/html/rfc9051]. + # - Updates #fetch and #uid_fetch with the +BINARY+, +BINARY.PEEK+, and + # +BINARY.SIZE+ items. See FetchData#binary and FetchData#binary_size. + # + # >>> + # *NOTE:* The binary extension the #append command is _not_ supported yet. + # + # ==== RFC3691: +UNSELECT+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included + # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #unselect: Closes the mailbox and returns to the "_authenticated_" state, + # without expunging any messages. + # + # ==== RFC4314: +ACL+ + # - #getacl: lists the authenticated user's access rights to a mailbox. + # - #setacl: sets the access rights for a user on a mailbox + # >>> + # *NOTE:* +DELETEACL+, +LISTRIGHTS+, and +MYRIGHTS+ are not supported yet. + # + # ==== RFC4315: +UIDPLUS+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included + # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #uid_expunge: Restricts #expunge to only remove the specified UIDs. + # - Updates #select, #examine with the +UIDNOTSTICKY+ ResponseCode + # - Updates #append with the +APPENDUID+ ResponseCode + # - Updates #copy, #move with the +COPYUID+ ResponseCode + # + # ==== RFC4959: +SASL-IR+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051]. + # - Updates #authenticate with the option to send an initial response. + # + # ==== RFC5161: +ENABLE+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included + # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #enable: Enables backwards incompatible server extensions. + # + # ==== RFC5256: +SORT+ + # - #sort, #uid_sort: An alternate version of #search or #uid_search which + # sorts the results by specified keys. + # ==== RFC5256: +THREAD+ + # - #thread, #uid_thread: An alternate version of #search or #uid_search, + # which arranges the results into ordered groups or threads according to a + # chosen algorithm. + # + # ==== +X-GM-EXT-1+ + # +X-GM-EXT-1+ is a non-standard Gmail extension. See {Google's + # documentation}[https://developers.google.com/gmail/imap/imap-extensions]. + # - Updates #fetch and #uid_fetch with support for +X-GM-MSGID+ (unique + # message ID), +X-GM-THRID+ (thread ID), and +X-GM-LABELS+ (Gmail labels). + # - Updates #search with the +X-GM-RAW+ search attribute. + # - #xlist: replaced by +SPECIAL-USE+ attributes in #list responses. + # + # *NOTE:* The +OBJECTID+ extension should replace +X-GM-MSGID+ and + # +X-GM-THRID+, but Gmail does not support it (as of 2023-11-10). + # + # ==== RFC6851: +MOVE+ + # Folded into IMAP4rev2[https://tools.ietf.org/html/rfc9051] and also included + # above with {Core IMAP commands}[rdoc-ref:Net::IMAP@Core+IMAP+commands]. + # - #move, #uid_move: Moves the specified messages to the end of the + # specified destination mailbox, expunging them from the current mailbox. + # + # ==== RFC6855: UTF8=ACCEPT, UTF8=ONLY + # + # - See #enable for information about support for UTF-8 string encoding. + # + # ==== RFC7162: +CONDSTORE+ + # + # - Updates #enable with +CONDSTORE+ parameter. +CONDSTORE+ will also be + # enabled by using any of the extension's command parameters, listed below. + # - Updates #status with the +HIGHESTMODSEQ+ status attribute. + # - Updates #select and #examine with the +condstore+ modifier, and adds + # either a +HIGHESTMODSEQ+ or +NOMODSEQ+ ResponseCode to the responses. + # - Updates #search, #uid_search, #sort, and #uid_sort with the +MODSEQ+ + # search criterion, and adds SearchResult#modseq to the search response. + # - Updates #thread and #uid_thread with the +MODSEQ+ search criterion + # (but thread responses are unchanged). + # - Updates #fetch and #uid_fetch with the +changedsince+ modifier and + # +MODSEQ+ FetchData attribute. + # - Updates #store and #uid_store with the +unchangedsince+ modifier and adds + # the +MODIFIED+ ResponseCode to the tagged response. + # + # ==== RFC8438: STATUS=SIZE + # - Updates #status with the +SIZE+ status attribute. + # + # ==== RFC8474: +OBJECTID+ + # - Adds +MAILBOXID+ ResponseCode to #create tagged response. + # - Adds +MAILBOXID+ ResponseCode to #select and #examine untagged response. + # - Updates #fetch and #uid_fetch with the +EMAILID+ and +THREADID+ items. + # See FetchData#emailid and FetchData#emailid. + # - Updates #status with support for the +MAILBOXID+ status attribute. + # + # == References + # + # [{IMAP4rev1}[https://www.rfc-editor.org/rfc/rfc3501.html]]:: + # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - \VERSION 4rev1", + # RFC 3501, DOI 10.17487/RFC3501, March 2003, + # . + # + # [IMAP-ABNF-EXT[https://www.rfc-editor.org/rfc/rfc4466.html]]:: + # Melnikov, A. and C. Daboo, "Collected Extensions to IMAP4 ABNF", + # RFC 4466, DOI 10.17487/RFC4466, April 2006, + # . + # + # Note: Net::IMAP cannot parse the entire RFC4466 grammar yet. + # + # [{IMAP4rev2}[https://www.rfc-editor.org/rfc/rfc9051.html]]:: + # Melnikov, A., Ed., and B. Leiba, Ed., "Internet Message Access Protocol + # (\IMAP) - Version 4rev2", RFC 9051, DOI 10.17487/RFC9051, August 2021, + # . + # + # Note: Net::IMAP is not fully compatible with IMAP4rev2 yet. + # + # [IMAP-IMPLEMENTATION[https://www.rfc-editor.org/info/rfc2683]]:: + # Leiba, B., "IMAP4 Implementation Recommendations", + # RFC 2683, DOI 10.17487/RFC2683, September 1999, + # . + # + # [IMAP-MULTIACCESS[https://www.rfc-editor.org/info/rfc2180]]:: + # Gahrns, M., "IMAP4 Multi-Accessed Mailbox Practice", RFC 2180, DOI + # 10.17487/RFC2180, July 1997, . + # + # [UTF7[https://tools.ietf.org/html/rfc2152]]:: + # Goldsmith, D. and M. Davis, "UTF-7 A Mail-Safe Transformation Format of + # Unicode", RFC 2152, DOI 10.17487/RFC2152, May 1997, + # . + # + # === Message envelope and body structure + # + # [RFC5322[https://tools.ietf.org/html/rfc5322]]:: + # Resnick, P., Ed., "Internet Message Format", + # RFC 5322, DOI 10.17487/RFC5322, October 2008, + # . + # + # Note: obsoletes + # RFC-2822[https://tools.ietf.org/html/rfc2822] (April 2001) and + # RFC-822[https://tools.ietf.org/html/rfc822] (August 1982). + # + # [CHARSET[https://tools.ietf.org/html/rfc2978]]:: + # Freed, N. and J. Postel, "IANA Charset Registration Procedures", BCP 19, + # RFC 2978, DOI 10.17487/RFC2978, October 2000, + # . + # + # [DISPOSITION[https://tools.ietf.org/html/rfc2183]]:: + # Troost, R., Dorner, S., and K. Moore, Ed., "Communicating Presentation + # Information in Internet Messages: The Content-Disposition Header + # Field", RFC 2183, DOI 10.17487/RFC2183, August 1997, + # . + # + # [MIME-IMB[https://tools.ietf.org/html/rfc2045]]:: + # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions + # (MIME) Part One: Format of Internet Message Bodies", + # RFC 2045, DOI 10.17487/RFC2045, November 1996, + # . + # + # [MIME-IMT[https://tools.ietf.org/html/rfc2046]]:: + # Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions + # (MIME) Part Two: Media Types", RFC 2046, DOI 10.17487/RFC2046, + # November 1996, . + # + # [MIME-HDRS[https://tools.ietf.org/html/rfc2047]]:: + # Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part Three: + # Message Header Extensions for Non-ASCII Text", + # RFC 2047, DOI 10.17487/RFC2047, November 1996, + # . + # + # [RFC2231[https://tools.ietf.org/html/rfc2231]]:: + # Freed, N. and K. Moore, "MIME Parameter Value and Encoded Word + # Extensions: Character Sets, Languages, and Continuations", + # RFC 2231, DOI 10.17487/RFC2231, November 1997, + # . + # + # [I18n-HDRS[https://tools.ietf.org/html/rfc6532]]:: + # Yang, A., Steele, S., and N. Freed, "Internationalized Email Headers", + # RFC 6532, DOI 10.17487/RFC6532, February 2012, + # . + # + # [LANGUAGE-TAGS[https://www.rfc-editor.org/info/rfc3282]]:: + # Alvestrand, H., "Content Language Headers", + # RFC 3282, DOI 10.17487/RFC3282, May 2002, + # . + # + # [LOCATION[https://www.rfc-editor.org/info/rfc2557]]:: + # Palme, J., Hopmann, A., and N. Shelness, "MIME Encapsulation of + # Aggregate Documents, such as HTML (MHTML)", + # RFC 2557, DOI 10.17487/RFC2557, March 1999, + # . + # + # [MD5[https://tools.ietf.org/html/rfc1864]]:: + # Myers, J. and M. Rose, "The Content-MD5 Header Field", + # RFC 1864, DOI 10.17487/RFC1864, October 1995, + # . + # + # [RFC3503[https://tools.ietf.org/html/rfc3503]]:: + # Melnikov, A., "Message Disposition Notification (MDN) + # profile for Internet Message Access Protocol (IMAP)", + # RFC 3503, DOI 10.17487/RFC3503, March 2003, + # . + # + # === \IMAP Extensions + # + # [QUOTA[https://tools.ietf.org/html/rfc9208]]:: + # Melnikov, A., "IMAP QUOTA Extension", RFC 9208, DOI 10.17487/RFC9208, + # March 2022, . + # + # Note: obsoletes + # RFC-2087[https://tools.ietf.org/html/rfc2087] (January 1997). + # Net::IMAP does not fully support the RFC9208 updates yet. + # [IDLE[https://tools.ietf.org/html/rfc2177]]:: + # Leiba, B., "IMAP4 IDLE command", RFC 2177, DOI 10.17487/RFC2177, + # June 1997, . + # [NAMESPACE[https://tools.ietf.org/html/rfc2342]]:: + # Gahrns, M. and C. Newman, "IMAP4 Namespace", RFC 2342, + # DOI 10.17487/RFC2342, May 1998, . + # [ID[https://tools.ietf.org/html/rfc2971]]:: + # Showalter, T., "IMAP4 ID extension", RFC 2971, DOI 10.17487/RFC2971, + # October 2000, . + # [BINARY[https://tools.ietf.org/html/rfc3516]]:: + # Nerenberg, L., "IMAP4 Binary Content Extension", RFC 3516, + # DOI 10.17487/RFC3516, April 2003, + # . + # [ACL[https://tools.ietf.org/html/rfc4314]]:: + # Melnikov, A., "IMAP4 Access Control List (ACL) Extension", RFC 4314, + # DOI 10.17487/RFC4314, December 2005, + # . + # [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]]:: + # Crispin, M., "Internet Message Access Protocol (\IMAP) - UIDPLUS + # extension", RFC 4315, DOI 10.17487/RFC4315, December 2005, + # . + # [SORT[https://tools.ietf.org/html/rfc5256]]:: + # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and + # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008, + # . + # [THREAD[https://tools.ietf.org/html/rfc5256]]:: + # Crispin, M. and K. Murchison, "Internet Message Access Protocol - SORT and + # THREAD Extensions", RFC 5256, DOI 10.17487/RFC5256, June 2008, + # . + # [RFC5530[https://www.rfc-editor.org/rfc/rfc5530.html]]:: + # Gulbrandsen, A., "IMAP Response Codes", RFC 5530, DOI 10.17487/RFC5530, + # May 2009, . + # [MOVE[https://tools.ietf.org/html/rfc6851]]:: + # Gulbrandsen, A. and N. Freed, Ed., "Internet Message Access Protocol + # (\IMAP) - MOVE Extension", RFC 6851, DOI 10.17487/RFC6851, January 2013, + # . + # [UTF8=ACCEPT[https://tools.ietf.org/html/rfc6855]]:: + # [UTF8=ONLY[https://tools.ietf.org/html/rfc6855]]:: + # Resnick, P., Ed., Newman, C., Ed., and S. Shen, Ed., + # "IMAP Support for UTF-8", RFC 6855, DOI 10.17487/RFC6855, March 2013, + # . + # [CONDSTORE[https://tools.ietf.org/html/rfc7162]]:: + # [QRESYNC[https://tools.ietf.org/html/rfc7162]]:: + # Melnikov, A. and D. Cridland, "IMAP Extensions: Quick Flag Changes + # Resynchronization (CONDSTORE) and Quick Mailbox Resynchronization + # (QRESYNC)", RFC 7162, DOI 10.17487/RFC7162, May 2014, + # . + # [OBJECTID[https://tools.ietf.org/html/rfc8474]]:: + # Gondwana, B., Ed., "IMAP Extension for Object Identifiers", + # RFC 8474, DOI 10.17487/RFC8474, September 2018, + # . + # + # === IANA registries + # * {IMAP Capabilities}[http://www.iana.org/assignments/imap4-capabilities] + # * {IMAP Response Codes}[https://www.iana.org/assignments/imap-response-codes/imap-response-codes.xhtml] + # * {IMAP Mailbox Name Attributes}[https://www.iana.org/assignments/imap-mailbox-name-attributes/imap-mailbox-name-attributes.xhtml] + # * {IMAP and JMAP Keywords}[https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml] + # * {IMAP Threading Algorithms}[https://www.iana.org/assignments/imap-threading-algorithms/imap-threading-algorithms.xhtml] + # * {SASL Mechanisms and SASL SCRAM Family Mechanisms}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # * {Service Name and Transport Protocol Port Number Registry}[https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml]: + # +imap+: tcp/143, +imaps+: tcp/993 + # * {GSSAPI/Kerberos/SASL Service Names}[https://www.iana.org/assignments/gssapi-service-names/gssapi-service-names.xhtml]: + # +imap+ + # * {Character sets}[https://www.iana.org/assignments/character-sets/character-sets.xhtml] + # ===== For currently unsupported features: + # * {IMAP Quota Resource Types}[http://www.iana.org/assignments/imap4-capabilities#imap-capabilities-2] + # * {LIST-EXTENDED options and responses}[https://www.iana.org/assignments/imap-list-extended/imap-list-extended.xhtml] + # * {IMAP METADATA Server Entry and Mailbox Entry Registries}[https://www.iana.org/assignments/imap-metadata/imap-metadata.xhtml] + # * {IMAP ANNOTATE Extension Entries and Attributes}[https://www.iana.org/assignments/imap-annotate-extension/imap-annotate-extension.xhtml] + # * {IMAP URLAUTH Access Identifiers and Prefixes}[https://www.iana.org/assignments/urlauth-access-ids/urlauth-access-ids.xhtml] + # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml] + # + class IMAP < Protocol + VERSION = "0.4.9.1" + + # Aliases for supported capabilities, to be used with the #enable command. + ENABLE_ALIASES = { + utf8: "UTF8=ACCEPT", + "UTF8=ONLY" => "UTF8=ACCEPT", + }.freeze + + autoload :SASL, File.expand_path("imap/sasl", __dir__) + autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__) + autoload :StringPrep, File.expand_path("imap/stringprep", __dir__) + + include MonitorMixin + if defined?(OpenSSL::SSL) + include OpenSSL + include SSL + end + + # Returns the debug mode. + def self.debug + return @@debug + end + + # Sets the debug mode. + def self.debug=(val) + return @@debug = val + end + + # The default port for IMAP connections, port 143 + def self.default_port + return PORT + end + + # The default port for IMAPS connections, port 993 + def self.default_tls_port + return SSL_PORT + end + + class << self + alias default_imap_port default_port + alias default_imaps_port default_tls_port + alias default_ssl_port default_tls_port + end + + # Returns the initial greeting the server, an UntaggedResponse. + attr_reader :greeting + + # Seconds to wait until a connection is opened. + # If the IMAP object cannot open a connection within this time, + # it raises a Net::OpenTimeout exception. The default value is 30 seconds. + attr_reader :open_timeout + + # Seconds to wait until an IDLE response is received. + attr_reader :idle_response_timeout + + # The hostname this client connected to + attr_reader :host + + # The port this client connected to + attr_reader :port + + # Returns the + # {SSLContext}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html] + # used by the SSLSocket when TLS is attempted, even when the TLS handshake + # is unsuccessful. The context object will be frozen. + # + # Returns +nil+ for a plaintext connection. + attr_reader :ssl_ctx + + # Returns the parameters that were sent to #ssl_ctx + # {set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params] + # when the connection tries to use TLS (even when unsuccessful). + # + # Returns +false+ for a plaintext connection. + attr_reader :ssl_ctx_params + + # Creates a new Net::IMAP object and connects it to the specified + # +host+. + # + # ==== Options + # + # Accepts the following options: + # + # [port] + # Port number. Defaults to 993 when +ssl+ is truthy, and 143 otherwise. + # + # [ssl] + # If +true+, the connection will use TLS with the default params set by + # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]. + # If +ssl+ is a hash, it's passed to + # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]; + # the keys are names of attribute assignment methods on + # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]. + # + # [open_timeout] + # Seconds to wait until a connection is opened + # [idle_response_timeout] + # Seconds to wait until an IDLE response is received + # + # See DeprecatedClientOptions.new for deprecated arguments. + # + # ==== Examples + # + # Connect to cleartext port 143 at mail.example.com and recieve the server greeting: + # imap = Net::IMAP.new('mail.example.com', ssl: false) # => # + # imap.port => 143 + # imap.tls_verified? => false + # imap.greeting => name: ("OK" | "PREAUTH") => status + # status # => "OK" + # # The client is connected in the "Not Authenticated" state. + # + # Connect with TLS to port 993 + # imap = Net::IMAP.new('mail.example.com', ssl: true) # => # + # imap.port => 993 + # imap.tls_verified? => true + # imap.greeting => name: (/OK/i | /PREAUTH/i) => status + # case status + # in /OK/i + # # The client is connected in the "Not Authenticated" state. + # imap.authenticate("PLAIN", "joe_user", "joes_password") + # in /PREAUTH/i + # # The client is connected in the "Authenticated" state. + # end + # + # Connect with prior authentication, for example using an SSL certificate: + # ssl_ctx_params = { + # cert: OpenSSL::X509::Certificate.new(File.read("client.crt")), + # key: OpenSSL::PKey::EC.new(File.read('client.key')), + # extra_chain_cert: [ + # OpenSSL::X509::Certificate.new(File.read("intermediate.crt")), + # ], + # } + # imap = Net::IMAP.new('mail.example.com', ssl: ssl_ctx_params) + # imap.port => 993 + # imap.tls_verified? => true + # imap.greeting => name: "PREAUTH" + # # The client is connected in the "Authenticated" state. + # + # ==== Exceptions + # + # The most common errors are: + # + # [Errno::ECONNREFUSED] + # Connection refused by +host+ or an intervening firewall. + # [Errno::ETIMEDOUT] + # Connection timed out (possibly due to packets being dropped by an + # intervening firewall). + # [Errno::ENETUNREACH] + # There is no route to that network. + # [SocketError] + # Hostname not known or other socket error. + # [Net::IMAP::ByeResponseError] + # Connected to the host successfully, but it immediately said goodbye. + # + def initialize(host, port: nil, ssl: nil, + open_timeout: 30, idle_response_timeout: 5) + super() + # Config options + @host = host + @port = port || (ssl ? SSL_PORT : PORT) + @open_timeout = Integer(open_timeout) + @idle_response_timeout = Integer(idle_response_timeout) + @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(ssl) + + # Basic Client State + @utf8_strings = false + @debug_output_bol = true + @exception = nil + @greeting = nil + @capabilities = nil + + # Client Protocol Reciever + @parser = ResponseParser.new + @responses = Hash.new {|h, k| h[k] = [] } + @response_handlers = [] + @receiver_thread = nil + @receiver_thread_exception = nil + @receiver_thread_terminating = false + + # Client Protocol Sender (including state for currently running commands) + @tag_prefix = "RUBY" + @tagno = 0 + @tagged_responses = {} + @tagged_response_arrival = new_cond + @continued_command_tag = nil + @continuation_request_arrival = new_cond + @continuation_request_exception = nil + @idle_done_cond = nil + @logout_command_tag = nil + + # Connection + @tls_verified = false + @sock = tcp_socket(@host, @port) + start_tls_session if ssl_ctx + start_imap_connection + + # DEPRECATED: to remove in next version + @client_thread = Thread.current + end + + # Returns true after the TLS negotiation has completed and the remote + # hostname has been verified. Returns false when TLS has been established + # but peer verification was disabled. + def tls_verified?; @tls_verified end + + def client_thread # :nodoc: + warn "Net::IMAP#client_thread is deprecated and will be removed soon." + @client_thread + end + + # Disconnects from the server. + # + # Related: #logout, #logout! + def disconnect + return if disconnected? + begin + begin + # try to call SSL::SSLSocket#io. + @sock.io.shutdown + rescue NoMethodError + # @sock is not an SSL::SSLSocket. + @sock.shutdown + end + rescue Errno::ENOTCONN + # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms. + rescue Exception => e + @receiver_thread.raise(e) + end + @receiver_thread.join + synchronize do + @sock.close + end + raise e if e + end + + # Returns true if disconnected from the server. + # + # Related: #logout, #disconnect + def disconnected? + return @sock.closed? + end + + # Returns whether the server supports a given +capability+. When available, + # cached #capabilities are used without sending a new #capability command to + # the server. + # + # *NOTE:* Most Net::IMAP methods do not _currently_ modify their + # behaviour according to the server's advertised #capabilities. + # + # See Net::IMAP@Capabilities for more about \IMAP capabilities. + # + # Related: #auth_capable?, #capabilities, #capability, #enable + def capable?(capability) capabilities.include? capability.to_s.upcase end + alias capability? capable? + + # Returns the server capabilities. When available, cached capabilities are + # used without sending a new #capability command to the server. + # + # To ensure a case-insensitive comparison, #capable? can be used instead. + # + # *NOTE:* Most Net::IMAP methods do not _currently_ modify their + # behaviour according to the server's advertised #capabilities. + # + # See Net::IMAP@Capabilities for more about \IMAP capabilities. + # + # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable + def capabilities + @capabilities || capability + end + + # Returns the #authenticate mechanisms that the server claims to support. + # These are derived from the #capabilities with an AUTH= prefix. + # + # This may be different when the connection is cleartext or using TLS. Most + # servers will drop all AUTH= mechanisms from #capabilities after + # the connection has authenticated. + # + # imap = Net::IMAP.new(hostname, ssl: false) + # imap.capabilities # => ["IMAP4REV1", "LOGINDISABLED"] + # imap.auth_mechanisms # => [] + # + # imap.starttls + # imap.capabilities # => ["IMAP4REV1", "AUTH=PLAIN", "AUTH=XOAUTH2", + # # "AUTH=OAUTHBEARER"] + # imap.auth_mechanisms # => ["PLAIN", "XOAUTH2", "OAUTHBEARER"] + # + # imap.authenticate("XOAUTH2", username, oauth2_access_token) + # imap.auth_mechanisms # => [] + # + # Related: #authenticate, #auth_capable?, #capabilities + def auth_mechanisms + capabilities + .grep(/\AAUTH=/i) + .map { _1.delete_prefix("AUTH=") } + end + + # Returns whether the server supports a given SASL +mechanism+ for use with + # the #authenticate command. The +mechanism+ is supported when + # #capabilities includes "AUTH=#{mechanism.to_s.upcase}". When + # available, cached capabilities are used without sending a new #capability + # command to the server. + # + # imap.capable? "AUTH=PLAIN" # => true + # imap.auth_capable? "PLAIN" # => true + # imap.auth_capable? "blurdybloop" # => false + # + # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities + def auth_capable?(mechanism) + capable? "AUTH=#{mechanism}" + end + + # Returns whether capabilities have been cached. When true, #capable? and + # #capabilities don't require sending a #capability command to the server. + # + # See Net::IMAP@Capabilities for more about \IMAP capabilities. + # + # Related: #capable?, #capability, #clear_cached_capabilities + def capabilities_cached? + !!@capabilities + end + + # Clears capabilities that have been remembered by the Net::IMAP client. + # This forces a #capability command to be sent the next time a #capabilities + # query method is called. + # + # Net::IMAP automatically discards its cached capabilities when they can + # change. Explicitly calling this _should_ be unnecessary for well-behaved + # servers. + # + # Related: #capable?, #capability, #capabilities_cached? + def clear_cached_capabilities + synchronize do + clear_responses("CAPABILITY") + @capabilities = nil + end + end + + # Sends a {CAPABILITY command [IMAP4rev1 §6.1.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.1] + # and returns an array of capabilities that are supported by the server. + # The result is stored for use by #capable? and #capabilities. + # + # *NOTE:* Most Net::IMAP methods do not _currently_ modify their + # behaviour according to the server's advertised #capabilities. + # + # Net::IMAP automatically stores and discards capability data according to + # the requirements and recommendations in + # {IMAP4rev2 §6.1.1}[https://www.rfc-editor.org/rfc/rfc9051#section-6.1.1], + # {§6.2}[https://www.rfc-editor.org/rfc/rfc9051#section-6.2], and + # {§7.1}[https://www.rfc-editor.org/rfc/rfc9051#section-7.1]. + # Use #capable?, #auth_capable?, or #capabilities to this cache and avoid + # sending the #capability command unnecessarily. + # + # See Net::IMAP@Capabilities for more about \IMAP capabilities. + # + # Related: #capable?, #auth_capable?, #capability, #enable + def capability + synchronize do + send_command("CAPABILITY") + @capabilities = clear_responses("CAPABILITY").last.freeze + end + end + + # Sends an {ID command [RFC2971 §3.1]}[https://www.rfc-editor.org/rfc/rfc2971#section-3.1] + # and returns a hash of the server's response, or nil if the server does not + # identify itself. + # + # Note that the user should first check if the server supports the ID + # capability. For example: + # + # if capable?(:ID) + # id = imap.id( + # name: "my IMAP client (ruby)", + # version: MyIMAP::VERSION, + # "support-url": "mailto:bugs@example.com", + # os: RbConfig::CONFIG["host_os"], + # ) + # end + # + # See [ID[https://tools.ietf.org/html/rfc2971]] for field definitions. + # + # ===== Capabilities + # + # The server's capabilities must include +ID+ + # [RFC2971[https://tools.ietf.org/html/rfc2971]]. + def id(client_id=nil) + synchronize do + send_command("ID", ClientID.new(client_id)) + clear_responses("ID").last + end + end + + # Sends a {NOOP command [IMAP4rev1 §6.1.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.2] + # to the server. + # + # This allows the server to send unsolicited untagged EXPUNGE #responses, + # but does not execute any client request. \IMAP servers are permitted to + # send unsolicited untagged responses at any time, except for +EXPUNGE+: + # + # * +EXPUNGE+ can only be sent while a command is in progress. + # * +EXPUNGE+ must _not_ be sent during #fetch, #store, or #search. + # * +EXPUNGE+ may be sent during #uid_fetch, #uid_store, or #uid_search. + # + # Related: #idle, #check + def noop + send_command("NOOP") + end + + # Sends a {LOGOUT command [IMAP4rev1 §6.1.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.1.3] + # to inform the command to inform the server that the client is done with + # the connection. + # + # Related: #disconnect, #logout! + def logout + send_command("LOGOUT") + end + + # Calls #logout then, after receiving the TaggedResponse for the +LOGOUT+, + # calls #disconnect. Returns the TaggedResponse from +LOGOUT+. Returns + # +nil+ when the client is already disconnected, in contrast to #logout + # which raises an exception. + # + # If #logout raises a StandardError, a warning will be printed but the + # exception will not be re-raised. + # + # This is useful in situations where the connection must be dropped, for + # example for security or after tests. If logout errors need to be handled, + # use #logout and #disconnect instead. + # + # Related: #logout, #disconnect + def logout! + logout unless disconnected? + rescue => ex + warn "%s during logout!: %s" % [ + ex.class, host, port, ex + ] + ensure + disconnect + end + + # Sends a {STARTTLS command [IMAP4rev1 §6.2.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.1] + # to start a TLS session. + # + # Any +options+ are forwarded directly to + # {OpenSSL::SSL::SSLContext#set_params}[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html#method-i-set_params]; + # the keys are names of attribute assignment methods on + # SSLContext[https://docs.ruby-lang.org/en/master/OpenSSL/SSL/SSLContext.html]. + # + # See DeprecatedClientOptions#starttls for deprecated arguments. + # + # This method returns after TLS negotiation and hostname verification are + # both successful. Any error indicates that the connection has not been + # secured. + # + # *Note:* + # >>> + # Any #response_handlers added before STARTTLS should be aware that the + # TaggedResponse to STARTTLS is sent clear-text, _before_ TLS negotiation. + # TLS starts immediately _after_ that response. Any response code sent + # with the response (e.g. CAPABILITY) is insecure and cannot be trusted. + # + # Related: Net::IMAP.new, #login, #authenticate + # + # ===== Capability + # Clients should not call #starttls unless the server advertises the + # +STARTTLS+ capability. + # + # Server capabilities may change after #starttls, #login, and #authenticate. + # Cached #capabilities will be cleared when this method completes. + # + def starttls(**options) + @ssl_ctx_params, @ssl_ctx = build_ssl_ctx(options) + send_command("STARTTLS") do |resp| + if resp.kind_of?(TaggedResponse) && resp.name == "OK" + clear_cached_capabilities + clear_responses + start_tls_session + end + end + end + + # :call-seq: + # authenticate(mechanism, *, sasl_ir: true, registry: Net::IMAP::SASL.authenticators, **, &) -> ok_resp + # + # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2] + # to authenticate the client. If successful, the connection enters the + # "_authenticated_" state. + # + # +mechanism+ is the name of the \SASL authentication mechanism to be used. + # + # +sasl_ir+ allows or disallows sending an "initial response" (see the + # +SASL-IR+ capability, below). + # + # All other arguments are forwarded to the registered SASL authenticator for + # the requested mechanism. The documentation for each individual + # mechanism must be consulted for its specific parameters. + # + # Related: #login, #starttls, #auth_capable?, #auth_mechanisms + # + # ==== Mechanisms + # + # Each mechanism has different properties and requirements. Please consult + # the documentation for the specific mechanisms you are using: + # + # +ANONYMOUS+:: + # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator]. + # + # Allows the user to gain access to public services or resources without + # authenticating or disclosing an identity. + # + # +EXTERNAL+:: + # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator]. + # + # Authenticates using already established credentials, such as a TLS + # certificate or IPsec. + # + # +OAUTHBEARER+:: + # See OAuthBearerAuthenticator[rdoc-ref:Net::IMAP::SASL::OAuthBearerAuthenticator]. + # + # Login using an OAuth2 Bearer token. This is the standard mechanism + # for using OAuth2 with \SASL, but it is not yet deployed as widely as + # +XOAUTH2+. + # + # +PLAIN+:: + # See PlainAuthenticator[rdoc-ref:Net::IMAP::SASL::PlainAuthenticator]. + # + # Login using clear-text username and password. + # + # +SCRAM-SHA-1+:: + # +SCRAM-SHA-256+:: + # See ScramAuthenticator[rdoc-ref:Net::IMAP::SASL::ScramAuthenticator]. + # + # Login by username and password. The password is not sent to the + # server but is used in a salted challenge/response exchange. + # +SCRAM-SHA-1+ and +SCRAM-SHA-256+ are directly supported by + # Net::IMAP::SASL. New authenticators can easily be added for any other + # SCRAM-* mechanism if the digest algorithm is supported by + # OpenSSL::Digest. + # + # +XOAUTH2+:: + # See XOAuth2Authenticator[rdoc-ref:Net::IMAP::SASL::XOAuth2Authenticator]. + # + # Login using a username and an OAuth2 access token. Non-standard and + # obsoleted by +OAUTHBEARER+, but widely supported. + # + # See the {SASL mechanism + # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # for a list of all SASL mechanisms and their specifications. To register + # new authenticators, see Authenticators. + # + # ===== Deprecated mechanisms + # + # Obsolete mechanisms should be avoided, but are still available for + # backwards compatibility. See Net::IMAP::SASL@Deprecated+mechanisms. + # Using a deprecated mechanism will print a warning. + # + # ==== Capabilities + # + # "AUTH=#{mechanism}" capabilities indicate server support for + # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support + # before using a particular mechanism. + # + # if imap.auth_capable? "XOAUTH2" + # imap.authenticate "XOAUTH2", username, oauth2_access_token + # elsif imap.auth_capable? "PLAIN" + # imap.authenticate "PLAIN", username, password + # elsif !imap.capability? "LOGINDISABLED" + # imap.login username, password + # else + # raise "No acceptable authentication mechanism is available" + # end + # + # Although servers should list all supported \SASL mechanisms, they may + # allow authentication with an unlisted +mechanism+. + # + # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported + # and the appropriate "AUTH=#{mechanism}" capability is present, + # an "initial response" may be sent as an argument to the +AUTHENTICATE+ + # command, saving a round-trip. The SASL exchange allows for server + # challenges and client responses, but many mechanisms expect the client to + # "respond" first. The initial response will only be sent for + # "client-first" mechanisms. + # + # Server capabilities may change after #starttls, #login, and #authenticate. + # Previously cached #capabilities will be cleared when this method + # completes. If the TaggedResponse to #authenticate includes updated + # capabilities, they will be cached. + def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback) + mechanism = mechanism.to_s.tr("_", "-").upcase + authenticator = SASL.authenticator(mechanism, *creds, **props, &callback) + cmdargs = ["AUTHENTICATE", mechanism] + if sasl_ir && capable?("SASL-IR") && auth_capable?(mechanism) && + authenticator.respond_to?(:initial_response?) && + authenticator.initial_response? + response = authenticator.process(nil) + cmdargs << (response.empty? ? "=" : [response].pack("m0")) + end + result = send_command_with_continuations(*cmdargs) {|data| + challenge = data.unpack1("m") + response = authenticator.process challenge + [response].pack("m0") + } + if authenticator.respond_to?(:done?) && !authenticator.done? + logout! + raise SASL::AuthenticationIncomplete, result + end + @capabilities = capabilities_from_resp_code result + result + end + + # Sends a {LOGIN command [IMAP4rev1 §6.2.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.3] + # to identify the client and carries the plaintext +password+ authenticating + # this +user+. If successful, the connection enters the "_authenticated_" + # state. + # + # Using #authenticate {should be + # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command] + # over #login. The LOGIN command is not the same as #authenticate with the + # "LOGIN" +mechanism+. + # + # A Net::IMAP::NoResponseError is raised if authentication fails. + # + # Related: #authenticate, #starttls + # + # ===== Capabilities + # + # An IMAP client MUST NOT call #login when the server advertises the + # +LOGINDISABLED+ capability. + # + # if imap.capability? "LOGINDISABLED" + # raise "Remote server has disabled the login command" + # else + # imap.login username, password + # end + # + # Server capabilities may change after #starttls, #login, and #authenticate. + # Cached capabilities _must_ be invalidated after this method completes. + # The TaggedResponse to #login may include updated capabilities in its + # ResponseCode. + # + def login(user, password) + send_command("LOGIN", user, password) + .tap { @capabilities = capabilities_from_resp_code _1 } + end + + # Sends a {SELECT command [IMAP4rev1 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.1] + # to select a +mailbox+ so that messages in the +mailbox+ can be accessed. + # + # After you have selected a mailbox, you may retrieve the number of items in + # that mailbox from imap.responses("EXISTS", &:last), and the + # number of recent messages from imap.responses("RECENT", &:last). + # Note that these values can change if new messages arrive during a session + # or when existing messages are expunged; see #add_response_handler for a + # way to detect these events. + # + # When the +condstore+ keyword argument is true, the server is told to + # enable the extension. If +mailbox+ supports persistence of mod-sequences, + # the +HIGHESTMODSEQ+ ResponseCode will be sent as an untagged response to + # #select and all `FETCH` responses will include FetchData#modseq. + # Otherwise, the +NOMODSEQ+ ResponseCode will be sent. + # + # A Net::IMAP::NoResponseError is raised if the mailbox does not + # exist or is for some reason non-selectable. + # + # Related: #examine + # + # ===== Capabilities + # + # If [UIDPLUS[https://www.rfc-editor.org/rfc/rfc4315.html]] is supported, + # the server may return an untagged "NO" response with a "UIDNOTSTICKY" + # response code indicating that the mailstore does not support persistent + # UIDs: + # imap.responses("NO", &:last)&.code&.name == "UIDNOTSTICKY" + # + # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported, + # the +condstore+ keyword parameter may be used. + # imap.select("mbox", condstore: true) + # modseq = imap.responses("HIGHESTMODSEQ", &:last) + def select(mailbox, condstore: false) + args = ["SELECT", mailbox] + args << ["CONDSTORE"] if condstore + synchronize do + @responses.clear + send_command(*args) + end + end + + # Sends a {EXAMINE command [IMAP4rev1 §6.3.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.2] + # to select a +mailbox+ so that messages in the +mailbox+ can be accessed. + # Behaves the same as #select, except that the selected +mailbox+ is + # identified as read-only. + # + # A Net::IMAP::NoResponseError is raised if the mailbox does not + # exist or is for some reason non-examinable. + # + # Related: #select + def examine(mailbox, condstore: false) + args = ["EXAMINE", mailbox] + args << ["CONDSTORE"] if condstore + synchronize do + @responses.clear + send_command(*args) + end + end + + # Sends a {CREATE command [IMAP4rev1 §6.3.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.3] + # to create a new +mailbox+. + # + # A Net::IMAP::NoResponseError is raised if a mailbox with that name + # cannot be created. + # + # Related: #rename, #delete + def create(mailbox) + send_command("CREATE", mailbox) + end + + # Sends a {DELETE command [IMAP4rev1 §6.3.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.4] + # to remove the +mailbox+. + # + # A Net::IMAP::NoResponseError is raised if a mailbox with that name + # cannot be deleted, either because it does not exist or because the + # client does not have permission to delete it. + # + # Related: #create, #rename + def delete(mailbox) + send_command("DELETE", mailbox) + end + + # Sends a {RENAME command [IMAP4rev1 §6.3.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.5] + # to change the name of the +mailbox+ to +newname+. + # + # A Net::IMAP::NoResponseError is raised if a mailbox with the + # name +mailbox+ cannot be renamed to +newname+ for whatever + # reason; for instance, because +mailbox+ does not exist, or + # because there is already a mailbox with the name +newname+. + # + # Related: #create, #delete + def rename(mailbox, newname) + send_command("RENAME", mailbox, newname) + end + + # Sends a {SUBSCRIBE command [IMAP4rev1 §6.3.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.6] + # to add the specified +mailbox+ name to the server's set of "active" or + # "subscribed" mailboxes as returned by #lsub. + # + # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be + # subscribed to; for instance, because it does not exist. + # + # Related: #unsubscribe, #lsub, #list + def subscribe(mailbox) + send_command("SUBSCRIBE", mailbox) + end + + # Sends an {UNSUBSCRIBE command [IMAP4rev1 §6.3.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.7] + # to remove the specified +mailbox+ name from the server's set of "active" + # or "subscribed" mailboxes. + # + # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be + # unsubscribed from; for instance, because the client is not currently + # subscribed to it. + # + # Related: #subscribe, #lsub, #list + def unsubscribe(mailbox) + send_command("UNSUBSCRIBE", mailbox) + end + + # Sends a {LIST command [IMAP4rev1 §6.3.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.8] + # and returns a subset of names from the complete set of all names available + # to the client. +refname+ provides a context (for instance, a base + # directory in a directory-based mailbox hierarchy). +mailbox+ specifies a + # mailbox or (via wildcards) mailboxes under that context. Two wildcards + # may be used in +mailbox+: "*", which matches all characters + # *including* the hierarchy delimiter (for instance, "/" on a UNIX-hosted + # directory-based mailbox hierarchy); and "%", which matches all + # characters *except* the hierarchy delimiter. + # + # If +refname+ is empty, +mailbox+ is used directly to determine + # which mailboxes to match. If +mailbox+ is empty, the root + # name of +refname+ and the hierarchy delimiter are returned. + # + # The return value is an array of MailboxList. + # + # Related: #lsub, MailboxList + # + # ===== For example: + # + # imap.create("foo/bar") + # imap.create("foo/baz") + # p imap.list("", "foo/%") + # #=> [#, \\ + # #, \\ + # #] + # + #-- + # TODO: support LIST-EXTENDED extension [RFC5258]. Needed for IMAP4rev2. + #++ + def list(refname, mailbox) + synchronize do + send_command("LIST", refname, mailbox) + clear_responses("LIST") + end + end + + # Sends a {NAMESPACE command [RFC2342 §5]}[https://www.rfc-editor.org/rfc/rfc2342#section-5] + # and returns the namespaces that are available. The NAMESPACE command + # allows a client to discover the prefixes of namespaces used by a server + # for personal mailboxes, other users' mailboxes, and shared mailboxes. + # + # The return value is a Namespaces object which has +personal+, +other+, and + # +shared+ fields, each an array of Namespace objects. These arrays will be + # empty when the server responds with +nil+. + # + # Many \IMAP servers are configured with the default personal namespaces as + # ("" "/"): no prefix and the "+/+" hierarchy delimiter. In that + # common case, the naive client may not have any trouble naming mailboxes. + # But many servers are configured with the default personal namespace as + # e.g. ("INBOX." "."), placing all personal folders under INBOX, + # with "+.+" as the hierarchy delimiter. If the client does not check for + # this, but naively assumes it can use the same folder names for all + # servers, then folder creation (and listing, moving, etc) can lead to + # errors. + # + # From RFC2342[https://tools.ietf.org/html/rfc2342]: + # >>> + # Although typically a server will support only a single Personal + # Namespace, and a single Other User's Namespace, circumstances exist + # where there MAY be multiples of these, and a client MUST be prepared + # for them. If a client is configured such that it is required to create + # a certain mailbox, there can be circumstances where it is unclear which + # Personal Namespaces it should create the mailbox in. In these + # situations a client SHOULD let the user select which namespaces to + # create the mailbox in. + # + # Related: #list, Namespaces, Namespace + # + # ===== For example: + # + # if capable?("NAMESPACE") + # namespaces = imap.namespace + # if namespace = namespaces.personal.first + # prefix = namespace.prefix # e.g. "" or "INBOX." + # delim = namespace.delim # e.g. "/" or "." + # # personal folders should use the prefix and delimiter + # imap.create(prefix + "foo") + # imap.create(prefix + "bar") + # imap.create(prefix + %w[path to my folder].join(delim)) + # end + # end + # + # ===== Capabilities + # + # The server's capabilities must include +NAMESPACE+ + # [RFC2342[https://tools.ietf.org/html/rfc2342]]. + def namespace + synchronize do + send_command("NAMESPACE") + clear_responses("NAMESPACE").last + end + end + + # Sends a XLIST command, and returns a subset of names from + # the complete set of all names available to the client. + # +refname+ provides a context (for instance, a base directory + # in a directory-based mailbox hierarchy). +mailbox+ specifies + # a mailbox or (via wildcards) mailboxes under that context. + # Two wildcards may be used in +mailbox+: '*', which matches + # all characters *including* the hierarchy delimiter (for instance, + # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%', + # which matches all characters *except* the hierarchy delimiter. + # + # If +refname+ is empty, +mailbox+ is used directly to determine + # which mailboxes to match. If +mailbox+ is empty, the root + # name of +refname+ and the hierarchy delimiter are returned. + # + # The XLIST command is like the LIST command except that the flags + # returned refer to the function of the folder/mailbox, e.g. :Sent + # + # The return value is an array of MailboxList objects. For example: + # + # imap.create("foo/bar") + # imap.create("foo/baz") + # p imap.xlist("", "foo/%") + # #=> [#, \\ + # #, \\ + # #] + # + # Related: #list, MailboxList + # + # ===== Capabilities + # + # The server's capabilities must include +XLIST+, + # a deprecated Gmail extension (replaced by +SPECIAL-USE+). + #-- + # TODO: Net::IMAP doesn't yet have full SPECIAL-USE support. Supporting + # servers MAY return SPECIAL-USE attributes, but are not *required* to + # unless the SPECIAL-USE return option is supplied. + #++ + def xlist(refname, mailbox) + synchronize do + send_command("XLIST", refname, mailbox) + clear_responses("XLIST") + end + end + + # Sends a {GETQUOTAROOT command [RFC2087 §4.3]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.3] + # along with the specified +mailbox+. This command is generally available + # to both admin and user. If this mailbox exists, it returns an array + # containing objects of type MailboxQuotaRoot and MailboxQuota. + # + # Related: #getquota, #setquota, MailboxQuotaRoot, MailboxQuota + # + # ===== Capabilities + # + # The server's capabilities must include +QUOTA+ + # [RFC2087[https://tools.ietf.org/html/rfc2087]]. + def getquotaroot(mailbox) + synchronize do + send_command("GETQUOTAROOT", mailbox) + result = [] + result.concat(clear_responses("QUOTAROOT")) + result.concat(clear_responses("QUOTA")) + return result + end + end + + # Sends a {GETQUOTA command [RFC2087 §4.2]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.2] + # along with specified +mailbox+. If this mailbox exists, then an array + # containing a MailboxQuota object is returned. This command is generally + # only available to server admin. + # + # Related: #getquotaroot, #setquota, MailboxQuota + # + # ===== Capabilities + # + # The server's capabilities must include +QUOTA+ + # [RFC2087[https://tools.ietf.org/html/rfc2087]]. + def getquota(mailbox) + synchronize do + send_command("GETQUOTA", mailbox) + clear_responses("QUOTA") + end + end + + # Sends a {SETQUOTA command [RFC2087 §4.1]}[https://www.rfc-editor.org/rfc/rfc2087#section-4.1] + # along with the specified +mailbox+ and +quota+. If +quota+ is nil, then + # +quota+ will be unset for that mailbox. Typically one needs to be logged + # in as a server admin for this to work. + # + # Related: #getquota, #getquotaroot + # + # ===== Capabilities + # + # The server's capabilities must include +QUOTA+ + # [RFC2087[https://tools.ietf.org/html/rfc2087]]. + def setquota(mailbox, quota) + if quota.nil? + data = '()' + else + data = '(STORAGE ' + quota.to_s + ')' + end + send_command("SETQUOTA", mailbox, RawData.new(data)) + end + + # Sends a {SETACL command [RFC4314 §3.1]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.1] + # along with +mailbox+, +user+ and the +rights+ that user is to have on that + # mailbox. If +rights+ is nil, then that user will be stripped of any + # rights to that mailbox. + # + # Related: #getacl + # + # ===== Capabilities + # + # The server's capabilities must include +ACL+ + # [RFC4314[https://tools.ietf.org/html/rfc4314]]. + def setacl(mailbox, user, rights) + if rights.nil? + send_command("SETACL", mailbox, user, "") + else + send_command("SETACL", mailbox, user, rights) + end + end + + # Sends a {GETACL command [RFC4314 §3.3]}[https://www.rfc-editor.org/rfc/rfc4314#section-3.3] + # along with a specified +mailbox+. If this mailbox exists, an array + # containing objects of MailboxACLItem will be returned. + # + # Related: #setacl, MailboxACLItem + # + # ===== Capabilities + # + # The server's capabilities must include +ACL+ + # [RFC4314[https://tools.ietf.org/html/rfc4314]]. + def getacl(mailbox) + synchronize do + send_command("GETACL", mailbox) + clear_responses("ACL").last + end + end + + # Sends a {LSUB command [IMAP4rev1 §6.3.9]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.9] + # and returns a subset of names from the set of names that the user has + # declared as being "active" or "subscribed." +refname+ and +mailbox+ are + # interpreted as for #list. + # + # The return value is an array of MailboxList objects. + # + # Related: #subscribe, #unsubscribe, #list, MailboxList + def lsub(refname, mailbox) + synchronize do + send_command("LSUB", refname, mailbox) + clear_responses("LSUB") + end + end + + # Sends a {STATUS command [IMAP4rev1 §6.3.10]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.10] + # and returns the status of the indicated +mailbox+. +attr+ is a list of one + # or more attributes whose statuses are to be requested. + # + # The return value is a hash of attributes. Most status attributes return + # integer values, but some return other value types (documented below). + # + # A Net::IMAP::NoResponseError is raised if status values + # for +mailbox+ cannot be returned; for instance, because it + # does not exist. + # + # ===== Supported attributes + # + # +MESSAGES+:: The number of messages in the mailbox. + # + # +UIDNEXT+:: The next unique identifier value of the mailbox. + # + # +UIDVALIDITY+:: The unique identifier validity value of the mailbox. + # + # +UNSEEN+:: The number of messages without the \Seen flag. + # + # +DELETED+:: The number of messages with the \Deleted flag. + # + # +SIZE+:: + # The approximate size of the mailbox---must be greater than or equal to + # the sum of all messages' +RFC822.SIZE+ fetch item values. + # + # +HIGHESTMODSEQ+:: + # The highest mod-sequence value of all messages in the mailbox. See + # +CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. + # + # +MAILBOXID+:: + # A server-allocated unique _string_ identifier for the mailbox. See + # +OBJECTID+ {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. + # + # +RECENT+:: + # The number of messages with the \Recent flag. + # _NOTE:_ +RECENT+ was removed from IMAP4rev2. + # + # Unsupported attributes may be requested. The attribute value will be + # either an Integer or an ExtensionData object. + # + # ===== For example: + # + # p imap.status("inbox", ["MESSAGES", "RECENT"]) + # #=> {"RECENT"=>0, "MESSAGES"=>44} + # + # ===== Capabilities + # + # +SIZE+ requires the server's capabilities to include either +IMAP4rev2+ or + # STATUS=SIZE + # {[RFC8483]}[https://www.rfc-editor.org/rfc/rfc8483.html]. + # + # +DELETED+ requires the server's capabilities to include +IMAP4rev2+. + # + # +HIGHESTMODSEQ+ requires the server's capabilities to include +CONDSTORE+ + # {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]. + # + # +MAILBOXID+ requires the server's capabilities to include +OBJECTID+ + # {[RFC8474]}[https://www.rfc-editor.org/rfc/rfc8474.html]. + def status(mailbox, attr) + synchronize do + send_command("STATUS", mailbox, attr) + clear_responses("STATUS").last&.attr + end + end + + # Sends an {APPEND command [IMAP4rev1 §6.3.11]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.3.11] + # to append the +message+ to the end of the +mailbox+. The optional +flags+ + # argument is an array of flags initially passed to the new message. The + # optional +date_time+ argument specifies the creation time to assign to the + # new message; it defaults to the current time. + # + # For example: + # + # imap.append("inbox", <\\Deleted + # flag set. + # + # Related: #unselect + def close + send_command("CLOSE") + end + + # Sends an {UNSELECT command [RFC3691 §2]}[https://www.rfc-editor.org/rfc/rfc3691#section-3] + # {[IMAP4rev2 §6.4.2]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.2] + # to free the session resources for a mailbox and return to the + # "_authenticated_" state. This is the same as #close, except that + # \\Deleted messages are not removed from the mailbox. + # + # Related: #close + # + # ===== Capabilities + # + # The server's capabilities must include +UNSELECT+ + # [RFC3691[https://tools.ietf.org/html/rfc3691]]. + def unselect + send_command("UNSELECT") + end + + # Sends an {EXPUNGE command [IMAP4rev1 §6.4.3]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.3] + # Sends a EXPUNGE command to permanently remove from the currently + # selected mailbox all messages that have the \Deleted flag set. + # + # Related: #uid_expunge + def expunge + synchronize do + send_command("EXPUNGE") + clear_responses("EXPUNGE") + end + end + + # Sends a {UID EXPUNGE command [RFC4315 §2.1]}[https://www.rfc-editor.org/rfc/rfc4315#section-2.1] + # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9] + # to permanently remove all messages that have both the \\Deleted + # flag set and a UID that is included in +uid_set+. + # + # By using #uid_expunge instead of #expunge when resynchronizing with + # the server, the client can ensure that it does not inadvertantly + # remove any messages that have been marked as \\Deleted by other + # clients between the time that the client was last connected and + # the time the client resynchronizes. + # + # *Note:* + # >>> + # Although the command takes a set of UIDs for its argument, the + # server still returns regular EXPUNGE responses, which contain + # a sequence number. These will be deleted from + # #responses and this method returns them as an array of + # sequence number integers. + # + # Related: #expunge + # + # ===== Capabilities + # + # The server's capabilities must include +UIDPLUS+ + # [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]]. + def uid_expunge(uid_set) + synchronize do + send_command("UID EXPUNGE", MessageSet.new(uid_set)) + clear_responses("EXPUNGE") + end + end + + # Sends a {SEARCH command [IMAP4rev1 §6.4.4]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.4] + # to search the mailbox for messages that match the given searching + # criteria, and returns message sequence numbers. +keys+ can either be a + # string holding the entire search string, or a single-dimension array of + # search keywords and arguments. + # + # Returns a SearchResult object. SearchResult inherits from Array (for + # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+ + # capability has been enabled. + # + # Related: #uid_search + # + # ===== Search criteria + # + # For a full list of search criteria, + # see [{IMAP4rev1 §6.4.4}[https://www.rfc-editor.org/rfc/rfc3501.html#section-6.4.4]], + # or [{IMAP4rev2 §6.4.4}[https://www.rfc-editor.org/rfc/rfc9051.html#section-6.4.4]], + # in addition to documentation for + # any [CAPABILITIES[https://www.iana.org/assignments/imap-capabilities/imap-capabilities.xhtml]] + # reported by #capabilities which may define additional search filters, e.g: + # +CONDSTORE+, +WITHIN+, +FILTERS+, SEARCH=FUZZY, +OBJECTID+, or + # +SAVEDATE+. The following are some common search criteria: + # + # :: a set of message sequence numbers. "," indicates + # an interval, "+:+" indicates a range. For instance, + # "2,10:12,15" means "2,10,11,12,15". + # + # BEFORE :: messages with an internal date strictly before + # . The date argument has a format similar + # to 8-Aug-2002, and can be formatted using + # Net::IMAP.format_date. + # + # BODY :: messages that contain within their body. + # + # CC :: messages containing in their CC field. + # + # FROM :: messages that contain in their FROM field. + # + # NEW:: messages with the \Recent, but not the \Seen, flag set. + # + # NOT :: negate the following search key. + # + # OR :: "or" two search keys together. + # + # ON :: messages with an internal date exactly equal to , + # which has a format similar to 8-Aug-2002. + # + # SINCE :: messages with an internal date on or after . + # + # SUBJECT :: messages with in their subject. + # + # TO :: messages with in their TO field. + # + # ===== For example: + # + # p imap.search(["SUBJECT", "hello", "NOT", "NEW"]) + # #=> [1, 6, 7, 8] + # + # ===== Capabilities + # + # If [CONDSTORE[https://www.rfc-editor.org/rfc/rfc7162.html]] is supported + # and enabled for the selected mailbox, a non-empty SearchResult will + # include a +MODSEQ+ value. + # imap.select("mbox", condstore: true) + # result = imap.search(["SUBJECT", "hi there", "not", "new") + # #=> Net::IMAP::SearchResult[1, 6, 7, 8, modseq: 5594] + # result.modseq # => 5594 + def search(keys, charset = nil) + return search_internal("SEARCH", keys, charset) + end + + # Sends a {UID SEARCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] + # to search the mailbox for messages that match the given searching + # criteria, and returns unique identifiers (UIDs). + # + # Returns a SearchResult object. SearchResult inherits from Array (for + # backward compatibility) but adds SearchResult#modseq when the +CONDSTORE+ + # capability has been enabled. + # + # See #search for documentation of search criteria. + def uid_search(keys, charset = nil) + return search_internal("UID SEARCH", keys, charset) + end + + # :call-seq: + # fetch(set, attr, changedsince: nil) -> array of FetchData + # + # Sends a {FETCH command [IMAP4rev1 §6.4.5]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.5] + # to retrieve data associated with a message in the mailbox. + # + # The +set+ parameter is a number or a range between two numbers, + # or an array of those. The number is a message sequence number, + # where -1 represents a '*' for use in range notation like 100..-1 + # being interpreted as '100:*'. Beware that the +exclude_end?+ + # property of a Range object is ignored, and the contents of a + # range are independent of the order of the range endpoints as per + # the protocol specification, so 1...5, 5..1 and 5...1 are all + # equivalent to 1..5. + # + # +attr+ is a list of attributes to fetch; see the documentation + # for FetchData for a list of valid attributes. + # + # +changedsince+ is an optional integer mod-sequence. It limits results to + # messages with a mod-sequence greater than +changedsince+. + # + # The return value is an array of FetchData. + # + # Related: #uid_search, FetchData + # + # ===== For example: + # + # p imap.fetch(6..8, "UID") + # #=> [#98}>, \\ + # #99}>, \\ + # #100}>] + # p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]") + # #=> [#"Subject: test\r\n\r\n"}>] + # data = imap.uid_fetch(98, ["RFC822.SIZE", "INTERNALDATE"])[0] + # p data.seqno + # #=> 6 + # p data.attr["RFC822.SIZE"] + # #=> 611 + # p data.attr["INTERNALDATE"] + # #=> "12-Oct-2000 22:40:59 +0900" + # p data.attr["UID"] + # #=> 98 + # + # ===== Capabilities + # + # Many extensions define new message +attr+ names. See FetchData for a list + # of supported extension fields. + # + # The server's capabilities must include +CONDSTORE+ + # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the + # +changedsince+ argument. Using +changedsince+ implicitly enables the + # +CONDSTORE+ extension. + def fetch(set, attr, mod = nil, changedsince: nil) + fetch_internal("FETCH", set, attr, mod, changedsince: changedsince) + end + + # :call-seq: + # uid_fetch(set, attr, changedsince: nil) -> array of FetchData + # + # Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] + # to retrieve data associated with a message in the mailbox. + # + # Similar to #fetch, but the +set+ parameter contains unique identifiers + # instead of message sequence numbers. + # + # >>> + # *Note:* Servers _MUST_ implicitly include the +UID+ message data item as + # part of any +FETCH+ response caused by a +UID+ command, regardless of + # whether a +UID+ was specified as a message data item to the +FETCH+. + # + # Related: #fetch, FetchData + # + # ===== Capabilities + # Same as #fetch. + def uid_fetch(set, attr, mod = nil, changedsince: nil) + fetch_internal("UID FETCH", set, attr, mod, changedsince: changedsince) + end + + # :call-seq: + # store(set, attr, value, unchangedsince: nil) -> array of FetchData + # + # Sends a {STORE command [IMAP4rev1 §6.4.6]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.6] + # to alter data associated with messages in the mailbox, in particular their + # flags. + # + # +set+ is a number, an array of numbers, or a Range object. Each number is + # a message sequence number. + # + # +attr+ is the name of a data item to store. The semantics of +value+ + # varies based on +attr+: + # * When +attr+ is "FLAGS", the flags in +value+ replace the + # message's flag list. + # * When +attr+ is "+FLAGS", the flags in +value+ are added to + # the flags for the message. + # * When +attr+ is "-FLAGS", the flags in +value+ are removed + # from the message. + # + # +unchangedsince+ is an optional integer mod-sequence. It prohibits any + # changes to messages with +mod-sequence+ greater than the specified + # +unchangedsince+ value. A SequenceSet of any messages that fail this + # check will be returned in a +MODIFIED+ ResponseCode. + # + # The return value is an array of FetchData. + # + # Related: #uid_store + # + # ===== For example: + # + # p imap.store(6..8, "+FLAGS", [:Deleted]) + # #=> [#[:Seen, :Deleted]}>, + # #[:Seen, :Deleted]}>, + # #[:Seen, :Deleted]}>] + # + # ===== Capabilities + # + # Extensions may define new data items to be used with #store. + # + # The server's capabilities must include +CONDSTORE+ + # {[RFC7162]}[https://tools.ietf.org/html/rfc7162] in order to use the + # +unchangedsince+ argument. Using +unchangedsince+ implicitly enables the + # +CONDSTORE+ extension. + def store(set, attr, flags, unchangedsince: nil) + store_internal("STORE", set, attr, flags, unchangedsince: unchangedsince) + end + + # :call-seq: + # uid_store(set, attr, value, unchangedsince: nil) -> array of FetchData + # + # Sends a {UID STORE command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] + # to alter data associated with messages in the mailbox, in particular their + # flags. + # + # Similar to #store, but +set+ contains unique identifiers instead of + # message sequence numbers. + # + # Related: #store + # + # ===== Capabilities + # Same as #store. + def uid_store(set, attr, flags, unchangedsince: nil) + store_internal("UID STORE", set, attr, flags, unchangedsince: unchangedsince) + end + + # Sends a {COPY command [IMAP4rev1 §6.4.7]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.7] + # to copy the specified message(s) to the end of the specified destination + # +mailbox+. The +set+ parameter is a number, an array of numbers, or a + # Range object. The number is a message sequence number. + # + # Related: #uid_copy + # + # ===== Capabilities + # + # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is + # supported, the server's response should include a +COPYUID+ response code + # with UIDPlusData. This will report the UIDVALIDITY of the destination + # mailbox, the UID set of the source messages, and the assigned UID set of + # the moved messages. + def copy(set, mailbox) + copy_internal("COPY", set, mailbox) + end + + # Sends a {UID COPY command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8] + # to copy the specified message(s) to the end of the specified destination + # +mailbox+. + # + # Similar to #copy, but +set+ contains unique identifiers. + # + # ===== Capabilities + # + # +UIDPLUS+ affects #uid_copy the same way it affects #copy. + def uid_copy(set, mailbox) + copy_internal("UID COPY", set, mailbox) + end + + # Sends a {MOVE command [RFC6851 §3.1]}[https://www.rfc-editor.org/rfc/rfc6851#section-3.1] + # {[IMAP4rev2 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.8] + # to move the specified message(s) to the end of the specified destination + # +mailbox+. The +set+ parameter is a number, an array of numbers, or a + # Range object. The number is a message sequence number. + # + # Related: #uid_move + # + # ===== Capabilities + # + # The server's capabilities must include +MOVE+ + # [RFC6851[https://tools.ietf.org/html/rfc6851]]. + # + # If +UIDPLUS+ [RFC4315[https://www.rfc-editor.org/rfc/rfc4315.html]] is + # supported, the server's response should include a +COPYUID+ response code + # with UIDPlusData. This will report the UIDVALIDITY of the destination + # mailbox, the UID set of the source messages, and the assigned UID set of + # the moved messages. + # + def move(set, mailbox) + copy_internal("MOVE", set, mailbox) + end + + # Sends a {UID MOVE command [RFC6851 §3.2]}[https://www.rfc-editor.org/rfc/rfc6851#section-3.2] + # {[IMAP4rev2 §6.4.9]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.4.9] + # to move the specified message(s) to the end of the specified destination + # +mailbox+. + # + # Similar to #move, but +set+ contains unique identifiers. + # + # Related: #move + # + # ===== Capabilities + # + # Same as #move: The server's capabilities must include +MOVE+ + # [RFC6851[https://tools.ietf.org/html/rfc6851]]. +UIDPLUS+ also affects + # #uid_move the same way it affects #move. + def uid_move(set, mailbox) + copy_internal("UID MOVE", set, mailbox) + end + + # Sends a {SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] + # to search a mailbox for messages that match +search_keys+ and return an + # array of message sequence numbers, sorted by +sort_keys+. +search_keys+ + # are interpreted the same as for #search. + # + #-- + # TODO: describe +sort_keys+ + #++ + # + # Related: #uid_sort, #search, #uid_search, #thread, #uid_thread + # + # ===== For example: + # + # p imap.sort(["FROM"], ["ALL"], "US-ASCII") + # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9] + # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII") + # #=> [6, 7, 8, 1] + # + # ===== Capabilities + # + # The server's capabilities must include +SORT+ + # [RFC5256[https://tools.ietf.org/html/rfc5256]]. + def sort(sort_keys, search_keys, charset) + return sort_internal("SORT", sort_keys, search_keys, charset) + end + + # Sends a {UID SORT command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] + # to search a mailbox for messages that match +search_keys+ and return an + # array of unique identifiers, sorted by +sort_keys+. +search_keys+ are + # interpreted the same as for #search. + # + # Related: #sort, #search, #uid_search, #thread, #uid_thread + # + # ===== Capabilities + # + # The server's capabilities must include +SORT+ + # [RFC5256[https://tools.ietf.org/html/rfc5256]]. + def uid_sort(sort_keys, search_keys, charset) + return sort_internal("UID SORT", sort_keys, search_keys, charset) + end + + # Sends a {THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] + # to search a mailbox and return message sequence numbers in threaded + # format, as a ThreadMember tree. +search_keys+ are interpreted the same as + # for #search. + # + # The supported algorithms are: + # + # ORDEREDSUBJECT:: split into single-level threads according to subject, + # ordered by date. + # REFERENCES:: split into threads by parent/child relationships determined + # by which message is a reply to which. + # + # Unlike #search, +charset+ is a required argument. US-ASCII + # and UTF-8 are sample values. + # + # Related: #uid_thread, #search, #uid_search, #sort, #uid_sort + # + # ===== Capabilities + # + # The server's capabilities must include +THREAD+ + # [RFC5256[https://tools.ietf.org/html/rfc5256]]. + def thread(algorithm, search_keys, charset) + return thread_internal("THREAD", algorithm, search_keys, charset) + end + + # Sends a {UID THREAD command [RFC5256 §3]}[https://www.rfc-editor.org/rfc/rfc5256#section-3] + # Similar to #thread, but returns unique identifiers instead of + # message sequence numbers. + # + # Related: #thread, #search, #uid_search, #sort, #uid_sort + # + # ===== Capabilities + # + # The server's capabilities must include +THREAD+ + # [RFC5256[https://tools.ietf.org/html/rfc5256]]. + def uid_thread(algorithm, search_keys, charset) + return thread_internal("UID THREAD", algorithm, search_keys, charset) + end + + # Sends an {ENABLE command [RFC5161 §3.2]}[https://www.rfc-editor.org/rfc/rfc5161#section-3.1] + # {[IMAP4rev2 §6.3.1]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.1] + # to enable the specified server +capabilities+. Each capability may be an + # array, string, or symbol. Returns a list of the capabilities that were + # enabled. + # + # The +ENABLE+ command is only valid in the _authenticated_ state, before + # any mailbox is selected. + # + # Related: #capable?, #capabilities, #capability + # + # ===== Capabilities + # + # The server's capabilities must include + # +ENABLE+ [RFC5161[https://tools.ietf.org/html/rfc5161]] + # or +IMAP4REV2+ [RFC9051[https://tools.ietf.org/html/rfc9051]]. + # + # Additionally, the server capabilities must include a capability matching + # each enabled extension (usually the same name as the enabled extension). + # The following capabilities may be enabled: + # + # [+CONDSTORE+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]] + # + # Updates various commands to return +CONDSTORE+ extension responses. It + # is not necessary to explicitly enable +CONDSTORE+—using any of the + # command parameters defined by the extension will implicitly enable it. + # See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1]. + # + # [+:utf8+ --- an alias for "UTF8=ACCEPT"] + # + # In a future release, enable(:utf8) will enable either + # "UTF8=ACCEPT" or "IMAP4rev2", depending on server + # capabilities. + # + # ["UTF8=ACCEPT" [RFC6855[https://tools.ietf.org/html/rfc6855]]] + # + # The server's capabilities must include UTF8=ACCEPT _or_ + # UTF8=ONLY. + # + # This allows the server to send strings encoded as UTF-8 which might + # otherwise need to use a 7-bit encoding, such as {modified + # UTF-7}[::decode_utf7] for mailbox names, or RFC2047 encoded-words for + # message headers. + # + # *Note:* A future update may set string encodings slightly + # differently, e.g: "US-ASCII" when UTF-8 is not enabled, and "UTF-8" + # when it is. Currently, the encoding of strings sent as "quoted" or + # "text" will _always_ be "UTF-8", even when only ASCII characters are + # used (e.g. "Subject: Agenda") And currently, string "literals" sent + # by the server will always have an "ASCII-8BIT" (binary) + # encoding, even if they generally contain UTF-8 data, if they are + # text at all. + # + # ["UTF8=ONLY" [RFC6855[https://tools.ietf.org/html/rfc6855]]] + # + # A server that reports the UTF8=ONLY capability _requires_ that + # the client enable("UTF8=ACCEPT") before any mailboxes may be + # selected. For convenience, enable("UTF8=ONLY") is aliased to + # enable("UTF8=ACCEPT"). + # + # ===== Unsupported capabilities + # + # *Note:* Some extensions that use ENABLE permit the server to send syntax + # that Net::IMAP cannot parse, which may raise an exception and disconnect. + # Some extensions may work, but the support may be incomplete, untested, or + # experimental. + # + # Until a capability is documented here as supported, enabling it may result + # in undocumented behavior and a future release may update with incompatible + # behavior without warning or deprecation. + # + # Caution is advised. + # + def enable(*capabilities) + capabilities = capabilities + .flatten + .map {|e| ENABLE_ALIASES[e] || e } + .uniq + .join(' ') + synchronize do + send_command("ENABLE #{capabilities}") + result = clear_responses("ENABLED").last || [] + @utf8_strings ||= result.include? "UTF8=ACCEPT" + @utf8_strings ||= result.include? "IMAP4REV2" + result + end + end + + # Sends an {IDLE command [RFC2177 §3]}[https://www.rfc-editor.org/rfc/rfc6851#section-3] + # {[IMAP4rev2 §6.3.13]}[https://www.rfc-editor.org/rfc/rfc9051#section-6.3.13] + # that waits for notifications of new or expunged messages. Yields + # responses from the server during the IDLE. + # + # Use #idle_done to leave IDLE. + # + # If +timeout+ is given, this method returns after +timeout+ seconds passed. + # +timeout+ can be used for keep-alive. For example, the following code + # checks the connection for each 60 seconds. + # + # loop do + # imap.idle(60) do |res| + # ... + # end + # end + # + # Related: #idle_done, #noop, #check + # + # ===== Capabilities + # + # The server's capabilities must include +IDLE+ + # [RFC2177[https://tools.ietf.org/html/rfc2177]]. + def idle(timeout = nil, &response_handler) + raise LocalJumpError, "no block given" unless response_handler + + response = nil + + synchronize do + tag = Thread.current[:net_imap_tag] = generate_tag + put_string("#{tag} IDLE#{CRLF}") + + begin + add_response_handler(&response_handler) + @idle_done_cond = new_cond + @idle_done_cond.wait(timeout) + @idle_done_cond = nil + if @receiver_thread_terminating + raise @exception || Net::IMAP::Error.new("connection closed") + end + ensure + unless @receiver_thread_terminating + remove_response_handler(response_handler) + put_string("DONE#{CRLF}") + response = get_tagged_response(tag, "IDLE", @idle_response_timeout) + end + end + end + + return response + end + + # Leaves IDLE. + # + # Related: #idle + def idle_done + synchronize do + if @idle_done_cond.nil? + raise Net::IMAP::Error, "not during IDLE" + end + @idle_done_cond.signal + end + end + + # :call-seq: + # responses {|hash| ...} -> block result + # responses(type) {|array| ...} -> block result + # + # Yields unhandled responses and returns the result of the block. + # + # Unhandled responses are stored in a hash, with arrays of + # non-+nil+ UntaggedResponse#data keyed by UntaggedResponse#name + # and ResponseCode#data keyed by ResponseCode#name. Call without +type+ to + # yield the entire responses hash. Call with +type+ to yield only the array + # of responses for that type. + # + # For example: + # + # imap.select("inbox") + # p imap.responses("EXISTS", &:last) + # #=> 2 + # p imap.responses("UIDVALIDITY", &:last) + # #=> 968263756 + # + # >>> + # *Note:* Access to the responses hash is synchronized for thread-safety. + # The receiver thread and response_handlers cannot process new responses + # until the block completes. Accessing either the response hash or its + # response type arrays outside of the block is unsafe. + # + # Calling without a block is unsafe and deprecated. Future releases will + # raise ArgumentError unless a block is given. + # + # Previously unhandled responses are automatically cleared before entering a + # mailbox with #select or #examine. Long-lived connections can receive many + # unhandled server responses, which must be pruned or they will continually + # consume more memory. Update or clear the responses hash or arrays inside + # the block, or use #clear_responses. + # + # Only non-+nil+ data is stored. Many important response codes have no data + # of their own, but are used as "tags" on the ResponseText object they are + # attached to. ResponseText will be accessible by its response types: + # "+OK+", "+NO+", "+BAD+", "+BYE+", or "+PREAUTH+". + # + # TaggedResponse#data is not saved to #responses, nor is any + # ResponseCode#data on tagged responses. Although some command methods do + # return the TaggedResponse directly, #add_response_handler must be used to + # handle all response codes. + # + # Related: #clear_responses, #response_handlers, #greeting + def responses(type = nil) + if block_given? + synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) } + elsif type + raise ArgumentError, "Pass a block or use #clear_responses" + else + # warn("DEPRECATED: pass a block or use #clear_responses", uplevel: 1) + @responses + end + end + + # :call-seq: + # clear_responses -> hash + # clear_responses(type) -> array + # + # Clears and returns the unhandled #responses hash or the unhandled + # responses array for a single response +type+. + # + # Clearing responses is synchronized with other threads. The lock is + # released before returning. + # + # Related: #responses, #response_handlers + def clear_responses(type = nil) + synchronize { + if type + @responses.delete(type) || [] + else + @responses.dup.transform_values(&:freeze) + .tap { _1.default = [].freeze } + .tap { @responses.clear } + end + } + .freeze + end + + # Returns all response handlers, including those that are added internally + # by commands. Each response handler will be called with every new + # UntaggedResponse, TaggedResponse, and ContinuationRequest. + # + # Response handlers are called with a mutex inside the receiver thread. New + # responses cannot be processed and commands from other threads must wait + # until all response_handlers return. An exception will shut-down the + # receiver thread and close the connection. + # + # For thread-safety, the returned array is a frozen copy of the internal + # array. + # + # Related: #add_response_handler, #remove_response_handler + def response_handlers + synchronize { @response_handlers.clone.freeze } + end + + # Adds a response handler. For example, to detect when + # the server sends a new EXISTS response (which normally + # indicates new messages being added to the mailbox), + # add the following handler after selecting the + # mailbox: + # + # imap.add_response_handler { |resp| + # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS" + # puts "Mailbox now has #{resp.data} messages" + # end + # } + # + # Related: #remove_response_handler, #response_handlers + def add_response_handler(handler = nil, &block) + raise ArgumentError, "two Procs are passed" if handler && block + synchronize do + @response_handlers.push(block || handler) + end + end + + # Removes the response handler. + # + # Related: #add_response_handler, #response_handlers + def remove_response_handler(handler) + synchronize do + @response_handlers.delete(handler) + end + end + + private + + CRLF = "\r\n" # :nodoc: + PORT = 143 # :nodoc: + SSL_PORT = 993 # :nodoc: + + @@debug = false + + def start_imap_connection + @greeting = get_server_greeting + @capabilities = capabilities_from_resp_code @greeting + @receiver_thread = start_receiver_thread + rescue Exception + @sock.close + raise + end + + def get_server_greeting + greeting = get_response + raise Error, "No server greeting - connection closed" unless greeting + record_untagged_response_code greeting + raise ByeResponseError, greeting if greeting.name == "BYE" + greeting + end + + def start_receiver_thread + Thread.start do + receive_responses + rescue Exception => ex + @receiver_thread_exception = ex + # don't exit the thread with an exception + end + end + + def tcp_socket(host, port) + s = Socket.tcp(host, port, :connect_timeout => @open_timeout) + s.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, true) + s + rescue Errno::ETIMEDOUT + raise Net::OpenTimeout, "Timeout to open TCP connection to " + + "#{host}:#{port} (exceeds #{@open_timeout} seconds)" + end + + def receive_responses + connection_closed = false + until connection_closed + synchronize do + @exception = nil + end + begin + resp = get_response + rescue Exception => e + synchronize do + @sock.close + @exception = e + end + break + end + unless resp + synchronize do + @exception = EOFError.new("end of file reached") + end + break + end + begin + synchronize do + case resp + when TaggedResponse + @tagged_responses[resp.tag] = resp + @tagged_response_arrival.broadcast + case resp.tag + when @logout_command_tag + return + when @continued_command_tag + @continuation_request_exception = + RESPONSE_ERRORS[resp.name].new(resp) + @continuation_request_arrival.signal + end + when UntaggedResponse + record_untagged_response(resp) + if resp.name == "BYE" && @logout_command_tag.nil? + @sock.close + @exception = ByeResponseError.new(resp) + connection_closed = true + end + when ContinuationRequest + @continuation_request_arrival.signal + end + @response_handlers.each do |handler| + handler.call(resp) + end + end + rescue Exception => e + @exception = e + synchronize do + @tagged_response_arrival.broadcast + @continuation_request_arrival.broadcast + end + end + end + synchronize do + @receiver_thread_terminating = true + @tagged_response_arrival.broadcast + @continuation_request_arrival.broadcast + if @idle_done_cond + @idle_done_cond.signal + end + end + end + + def get_tagged_response(tag, cmd, timeout = nil) + if timeout + deadline = Time.now + timeout + end + until @tagged_responses.key?(tag) + raise @exception if @exception + if timeout + timeout = deadline - Time.now + if timeout <= 0 + return nil + end + end + @tagged_response_arrival.wait(timeout) + end + resp = @tagged_responses.delete(tag) + case resp.name + when /\A(?:OK)\z/ni + return resp + when /\A(?:NO)\z/ni + raise NoResponseError, resp + when /\A(?:BAD)\z/ni + raise BadResponseError, resp + else + disconnect + raise InvalidResponseError, "invalid tagged resp: %p" % [resp.raw.chomp] + end + end + + def get_response + buff = String.new + while true + s = @sock.gets(CRLF) + break unless s + buff.concat(s) + if /\{(\d+)\}\r\n/n =~ s + s = @sock.read($1.to_i) + buff.concat(s) + else + break + end + end + return nil if buff.length == 0 + if @@debug + $stderr.print(buff.gsub(/^/n, "S: ")) + end + return @parser.parse(buff) + end + + ############################# + # built-in response handlers + + # store name => [..., data] + def record_untagged_response(resp) + @responses[resp.name] << resp.data + record_untagged_response_code resp + end + + # store code.name => [..., code.data] + def record_untagged_response_code(resp) + return unless resp.data.is_a?(ResponseText) + return unless (code = resp.data.code) + @responses[code.name] << code.data + end + + # NOTE: only call this for greeting, login, and authenticate + def capabilities_from_resp_code(resp) + return unless %w[PREAUTH OK].any? { _1.casecmp? resp.name } + return unless (code = resp.data.code) + return unless code.name.casecmp?("CAPABILITY") + code.data.freeze + end + + ############################# + + # Calls send_command, yielding the text of each ContinuationRequest and + # responding with each block result. Returns TaggedResponse. Raises + # NoResponseError or BadResponseError. + def send_command_with_continuations(cmd, *args) + send_command(cmd, *args) do |server_response| + if server_response.instance_of?(ContinuationRequest) + client_response = yield server_response.data.text + put_string(client_response + CRLF) + end + end + end + + def send_command(cmd, *args, &block) + synchronize do + args.each do |i| + validate_data(i) + end + tag = generate_tag + put_string(tag + " " + cmd) + args.each do |i| + put_string(" ") + send_data(i, tag) + end + put_string(CRLF) + if cmd == "LOGOUT" + @logout_command_tag = tag + end + if block + add_response_handler(&block) + end + begin + return get_tagged_response(tag, cmd) + ensure + if block + remove_response_handler(block) + end + end + end + end + + def generate_tag + @tagno += 1 + return format("%s%04d", @tag_prefix, @tagno) + end + + def put_string(str) + @sock.print(str) + if @@debug + if @debug_output_bol + $stderr.print("C: ") + end + $stderr.print(str.gsub(/\n/n) { $'.empty? ? $& : "\nC: " }) + if /\n\z/n.match(str) + @debug_output_bol = true + else + @debug_output_bol = false + end + end + end + + def search_internal(cmd, keys, charset) + if keys.instance_of?(String) + keys = [RawData.new(keys)] + else + normalize_searching_criteria(keys) + end + synchronize do + if charset + send_command(cmd, "CHARSET", charset, *keys) + else + send_command(cmd, *keys) + end + clear_responses("SEARCH").last || [] + end + end + + def fetch_internal(cmd, set, attr, mod = nil, changedsince: nil) + if changedsince + mod ||= [] + mod << "CHANGEDSINCE" << Integer(changedsince) + end + case attr + when String then + attr = RawData.new(attr) + when Array then + attr = attr.map { |arg| + arg.is_a?(String) ? RawData.new(arg) : arg + } + end + + synchronize do + clear_responses("FETCH") + if mod + send_command(cmd, MessageSet.new(set), attr, mod) + else + send_command(cmd, MessageSet.new(set), attr) + end + clear_responses("FETCH") + end + end + + def store_internal(cmd, set, attr, flags, unchangedsince: nil) + attr = RawData.new(attr) if attr.instance_of?(String) + args = [MessageSet.new(set)] + args << ["UNCHANGEDSINCE", Integer(unchangedsince)] if unchangedsince + args << attr << flags + synchronize do + clear_responses("FETCH") + send_command(cmd, *args) + clear_responses("FETCH") + end + end + + def copy_internal(cmd, set, mailbox) + send_command(cmd, MessageSet.new(set), mailbox) + end + + def sort_internal(cmd, sort_keys, search_keys, charset) + if search_keys.instance_of?(String) + search_keys = [RawData.new(search_keys)] + else + normalize_searching_criteria(search_keys) + end + synchronize do + send_command(cmd, sort_keys, charset, *search_keys) + clear_responses("SORT").last || [] + end + end + + def thread_internal(cmd, algorithm, search_keys, charset) + if search_keys.instance_of?(String) + search_keys = [RawData.new(search_keys)] + else + normalize_searching_criteria(search_keys) + end + synchronize do + send_command(cmd, algorithm, charset, *search_keys) + clear_responses("THREAD").last || [] + end + end + + def normalize_searching_criteria(keys) + keys.collect! do |i| + case i + when -1, Range, Array + MessageSet.new(i) + else + i + end + end + end + + def build_ssl_ctx(ssl) + if ssl + params = (Hash.try_convert(ssl) || {}).freeze + context = SSLContext.new + context.set_params(params) + if defined?(VerifyCallbackProc) + context.verify_callback = VerifyCallbackProc + end + context.freeze + [params, context] + else + false + end + end + + def start_tls_session + raise "SSL extension not installed" unless defined?(OpenSSL::SSL) + raise "already using SSL" if @sock.kind_of?(OpenSSL::SSL::SSLSocket) + raise "cannot start TLS without SSLContext" unless ssl_ctx + @sock = SSLSocket.new(@sock, ssl_ctx) + @sock.sync_close = true + @sock.hostname = @host if @sock.respond_to? :hostname= + ssl_socket_connect(@sock, @open_timeout) + if ssl_ctx.verify_mode != VERIFY_NONE + @sock.post_connection_check(@host) + @tls_verified = true + end + end + + def sasl_adapter + SASLAdapter.new(self, &method(:send_command_with_continuations)) + end + + #-- + # We could get the saslprep method by extending the SASLprep module + # directly. It's done indirectly, so SASLprep can be lazily autoloaded, + # because most users won't need it. + #++ + # Delegates to Net::IMAP::StringPrep::SASLprep#saslprep. + def self.saslprep(string, **opts) + Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts) + end + + end +end + +require_relative "imap/errors" +require_relative "imap/command_data" +require_relative "imap/data_encoding" +require_relative "imap/flags" +require_relative "imap/response_data" +require_relative "imap/response_parser" +require_relative "imap/authenticators" + +require_relative "imap/deprecated_client_options" +Net::IMAP.prepend Net::IMAP::DeprecatedClientOptions diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/net-imap.gemspec ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/net-imap.gemspec --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/net-imap.gemspec 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/net-imap.gemspec 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +name = File.basename(__FILE__, ".gemspec") +version = ["lib", Array.new(name.count("-"), "..").join("/")].find do |dir| + break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb"), :encoding=> 'utf-8') do |line| + /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 + end rescue nil +end + +Gem::Specification.new do |spec| + spec.name = name + spec.version = version + spec.authors = ["Shugo Maeda", "nicholas a. evans"] + spec.email = ["shugo@ruby-lang.org", "nick@ekenosen.net"] + + spec.summary = %q{Ruby client api for Internet Message Access Protocol} + spec.description = %q{Ruby client api for Internet Message Access Protocol} + spec.homepage = "https://github.com/ruby/net-imap" + spec.required_ruby_version = Gem::Requirement.new(">= 2.7.3") + spec.licenses = ["Ruby", "BSD-2-Clause"] + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = spec.homepage + "/releases" + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do + `git ls-files -z 2>/dev/null`.split("\x0") + .reject {|f| f.match(%r{^(bin|test|spec|benchmarks|features|rfcs)/}) } + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_dependency "net-protocol" + spec.add_dependency "date" + + spec.add_development_dependency "digest" + spec.add_development_dependency "strscan" +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/benchmarks.rake ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/benchmarks.rake --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/benchmarks.rake 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/benchmarks.rake 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +PARSER_TEST_FIXTURES = FileList.new "test/net/imap/fixtures/response_parser/*.yml" +CLOBBER.include "benchmarks/parser.yml" +CLEAN.include "benchmarks/Gemfile-*" + +BENCHMARK_INIT = < PARSER_TEST_FIXTURES do |t| + require "yaml" + require "pathname" + require "net/imap" + + path = Pathname.new(__dir__) / "../test/net/imap/fixtures/response_parser" + files = path.glob("*.yml") + tests = files.flat_map {|file| + file.read + .gsub(%r{([-:]) !ruby/(object|struct):\S+}) { $1 } + .then { + YAML.safe_load(_1, filename: file, + permitted_classes: [Symbol, Regexp], aliases: true) + } + .fetch(:tests) + .select {|test_name, test| + :parser_assert_equal == test.fetch(:test_type) { + test.key?(:expected) ? :parser_assert_equal : :parser_pending + } + } + .map {|test_name, test| [test_name.to_s, test.fetch(:response)] } + } + + benchmarks = tests.map {|fixture_name, response| + {"name" => fixture_name.delete_prefix("test_"), + "prelude" => "response = -%s.b" % [response.dump], + "script" => "parser.parse(response)"} + } + .sort_by { _1["name"] } + + YAML.dump({"prelude" => BENCHMARK_INIT, "benchmark" => benchmarks}) + .then { File.write t.name, _1 } +end + +namespace :benchmarks do + desc "Generate benchmarks from fixture data" + task :generate => "benchmarks/parser.yml" + + desc "run the parser benchmarks comparing multiple gem versions" + task :compare => :generate do |task, args| + cd Pathname.new(__dir__) + ".." + current = `git describe --tags --dirty`.chomp + current = "dev" if current.empty? + versions = args.to_a + if versions.empty? + latest = %x{git describe --tags --abbrev=0 --match 'v*.*.*'}.chomp + versions = latest.empty? ? [] : [latest.delete_prefix("v")] + end + versions = versions.to_h { [_1, "Gemfile-v#{_1}"] } + cd "benchmarks" do + versions.each do |version, gemfile| + File.write gemfile, <<~RUBY + # frozen_string_literal: true + source "https://rubygems.org" + gem "net-imap", #{version.dump} + RUBY + end + versions = {current => "../Gemfile" , **versions}.map { + "%s::/usr/bin/env BUNDLE_GEMFILE=%s ruby" % _1 + }.join(";") + + extra = ENV.fetch("BENCHMARK_ARGS", "").shellsplit + + sh("benchmark-driver", + "--bundler", + "-e", versions, + "parser.yml", + *extra) + end + end + +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/rdoc.rake ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/rdoc.rake --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/rdoc.rake 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/rdoc.rake 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,70 @@ +# require "sdoc" +require "rdoc/task" +require_relative "../lib/net/imap" +require 'rdoc/rdoc' unless defined?(RDoc::Markup::ToHtml) + +module RDoc::Generator + module NetIMAP + + module RemoveRedundantParens + def param_seq + super.sub(/^\(\)\s*/, "") + end + end + + # See https://github.com/ruby/rdoc/pull/936 + module FixSectionComments + def markup(text) + @store ||= @parent&.store + super + end + def description; markup comment end + def comment; super || @comments&.first end + def parse(_comment_location = nil) super() end + end + + # render "[label] data" lists as tables. adapted from "hanna-nouveau" gem. + module LabelListTable + def list_item_start(list_item, list_type) + case list_type + when :NOTE + %(#{Array(list_item.label).map{|label| to_html(label)}.join("
")}) + else + super + end + end + + def list_end_for(list_type) + case list_type + when :NOTE then + "" + else + super + end + end + end + + end +end + +class RDoc::AnyMethod + prepend RDoc::Generator::NetIMAP::RemoveRedundantParens +end + +class RDoc::Context::Section + prepend RDoc::Generator::NetIMAP::FixSectionComments +end + +class RDoc::Markup::ToHtml + LIST_TYPE_TO_HTML[:NOTE] = ['', '
'] + prepend RDoc::Generator::NetIMAP::LabelListTable +end + +RDoc::Task.new do |doc| + doc.main = "README.md" + doc.title = "net-imap #{Net::IMAP::VERSION}" + doc.rdoc_dir = "doc" + doc.rdoc_files = FileList.new %w[lib/**/*.rb *.rdoc *.md] + doc.options << "--template-stylesheets" << "docs/styles.css" + # doc.generator = "hanna" +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/rfcs.rake ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/rfcs.rake --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/rfcs.rake 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/rfcs.rake 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +RFCS = { + + # Historic IMAP RFCs + 822 => "Internet Message Format (OBSOLETE)", + 1730 => "IMAP4 (OBSOLETE)", + 1731 => "IMAP4 Authentication Mechanisms (OBSOLETE)", + 2060 => "IMAP4rev1 (OBSOLETE)", + 2061 => "IMAP4 Compatibility with IMAP2bis", + 2062 => "Internet Message Access Protocol - Obsolete Syntax", + 2086 => "IMAP ACL (OBSOLETE)", + 2087 => "IMAP QUOTA (OBSOLETE)", + 2088 => "IMAP LITERAL+ (OBSOLETE)", + 2095 => "IMAP/POP AUTHorize Extension for CRAM-MD5 (OBSOLETE)", + 2192 => "IMAP URL Scheme (OBSOLETE)", + 2222 => "SASL (OBSOLETE)", + 2359 => "IMAP UIDPLUS (OBSOLETE)", + 2822 => "Internet Message Format (OBSOLETE)", + 3348 => "IMAP CHILDREN (OBSOLETED)", + 4551 => "IMAP CONDSTORE (OBSOLETE)", + 5162 => "IMAP QRESYNC (OBSOLETE)", + 6237 => "IMAP MULTISEARCH (OBSOLETE)", + + # Core IMAP RFCs + 3501 => "IMAP4rev1", # supported by nearly all email servers + 4466 => "Collected Extensions to IMAP4 ABNF", + 9051 => "IMAP4rev2", # not widely supported yet + + # RFC-9051 Normative References (not a complete list) + 2152 => "UTF-7", + 2180 => "IMAP4 Multi-Accessed Mailbox Practice", + 2683 => "IMAP4 Implementation Recommendations", + 3503 => "Message Disposition Notification (MDN) profile IMAP", + 5234 => "ABNF", + 5788 => "IMAP4 keyword registry", + + # Internet Message format and envelope and body structure + 5322 => "Internet Message Format (current)", + + 1864 => "[MD5]: The Content-MD5 Header Field", + 2045 => "[MIME-IMB]: MIME Part One: Format of Internet Message Bodies", + 2046 => "[MIME-IMT]: MIME Part Two: Media Types", + 2047 => "[MIME-HDRS]: MIME Part Three: Header Extensions for Non-ASCII Text", + 2183 => "[DISPOSITION]: The Content-Disposition Header", + 2231 => "MIME Parameter Value and Encoded Word Extensions: " \ + "Character Sets, Languages, and Continuations", + 2557 => "[LOCATION]: MIME Encapsulation of Aggregate Documents", + 2978 => "[CHARSET]: IANA Charset Registration Procedures, BCP 19", + 3282 => "[LANGUAGE-TAGS]: Content Language Headers", + 6532 => "[I18N-HDRS]: Internationalized Email Headers", + + # SASL + 4422 => "SASL, EXTERNAL", + + # stringprep + 3454 => "stringprep", + 4013 => "SASLprep", + 8265 => "PRECIS", # obsoletes SASLprep? + + # SASL mechanisms (not a complete list) + 2195 => "SASL CRAM-MD5", + 4505 => "SASL ANONYMOUS", + 4616 => "SASL PLAIN", + 4752 => "SASL GSSAPI (Kerberos V5)", + 5801 => "SASL GS2-*, GS2-KRB5", + 5802 => "SASL SCRAM-*, SCRAM-SHA-1, SCRAM-SHA1-PLUS", + 5803 => "LDAP Schema for Storing SCRAM Secrets", + 6331 => "SASL DIGEST-MD5", + 6595 => "SASL SAML20", + 6616 => "SASL OPENID20", + 7628 => "SASL OAUTH10A, OAUTHBEARER", + 7677 => "SASL SCRAM-SHA-256, SCRAM-SHA256-PLUS", + + # "Informational" RFCs + 1733 => "Distributed E-Mail Models in IMAP4", + 4549 => "Synchronization Operations for Disconnected IMAP4 Clients", + + # TLS and other security concerns + 2595 => "Using TLS with IMAP, POP3 and ACAP", + 6151 => "Updated Security Considerations for MD5 Message-Digest and HMAC-MD5", + 7525 => "Recommendations for Secure Use of TLS and DTLS", + 7818 => "Updated TLS Server Identity Check Procedure for Email Protocols", + 8314 => "Cleartext Considered Obsolete: Use of TLS for Email", + 8996 => "Deprecating TLS 1.0 and TLS 1.1,", + + # related email specifications + 6376 => "DomainKeys Identified Mail (DKIM) Signatures", + 6409 => "Message Submission for Mail", + + # Other IMAP4 "Standards Track" RFCs + 5092 => "IMAP URL Scheme", + 5593 => "IMAP URL Access Identifier Extension", + 5530 => "IMAP Response Codes", + 6186 => "Use of SRV Records for Locating Email Submission/Access Services", + 8305 => "Happy Eyeballs Version 2: Better Connectivity Using Concurrency", + + # IMAP4 Extensions + 2177 => "IMAP IDLE", + 2193 => "IMAP MAILBOX-REFERRALS", + 2221 => "IMAP LOGIN-REFERRALS", + 2342 => "IMAP NAMESPACE", + 2971 => "IMAP ID", + 3502 => "IMAP MULTIAPPEND", + 3516 => "IMAP BINARY", + 3691 => "IMAP UNSELECT", + 4314 => "IMAP ACL, RIGHTS=", + 4315 => "IMAP UIDPLUS", + 4467 => "IMAP URLAUTH", + 4469 => "IMAP CATENATE", + 4731 => "IMAP ESEARCH", + 4959 => "IMAP SASL-IR", + 4978 => "IMAP COMPRESS=DEFLATE", + 5032 => "IMAP WITHIN", + 5161 => "IMAP ENABLE", + 5182 => "IMAP SEARCHRES", + 5255 => "IMAP I18NLEVEL=1, I18NLEVEL=2, LANGUAGE", + 5256 => "IMAP SORT, THREAD", + 5257 => "IMAP ANNOTATE-EXPERIMENT-1", + 5258 => "IMAP LIST-EXTENDED", + 5259 => "IMAP CONVERT", + 5267 => "IMAP CONTEXT=SEARCH, CONTEXT=SORT, ESORT", + 5464 => "IMAP METADATA, METADATA-SERVER", + 5465 => "IMAP NOTIFY", + 5466 => "IMAP FILTERS", + 5524 => "IMAP URLAUTH=BINARY", # see also: [RFC Errata 6214] + 5550 => "IMAP URL-PARTIAL", + 5738 => "IMAP UTF8=ALL, UTF8=APPEND, UTF8=USER", # OBSOLETED by RFC6855 + 5819 => "IMAP LIST-STATUS", + 5957 => "IMAP SORT=DISPLAY", + 6154 => "IMAP SPECIAL-USE, CREATE-SPECIAL-USE", + 6203 => "IMAP SEARCH=FUZZY", + 6785 => "IMAP IMAPSIEVE=", + 6851 => "IMAP MOVE", + 6855 => "IMAP UTF8=ACCEPT, UTF8=ONLY", + 7162 => "IMAP CONDSTORE, QRESYNC", + 7377 => "IMAP MULTISEARCH", + 7888 => "IMAP LITERAL+, LITERAL-", + 7889 => "IMAP APPENDLIMIT", + 8437 => "IMAP UNAUTHENTICATE", + 8438 => "IMAP STATUS=SIZE", + 8440 => "IMAP LIST-MYRIGHTS", + 8474 => "IMAP OBJECTID", + 8508 => "IMAP REPLACE", + 8514 => "IMAP SAVEDATE", + 8970 => "IMAP PREVIEW", + 9208 => "IMAP QUOTA, QUOTA=, QUOTASET", + + # etc... + 3629 => "UTF8", + 6857 => "Post-Delivery Message Downgrading for I18n Email Messages", + +}.freeze + +task :rfcs => RFCS.keys.map {|n| "rfcs/rfc%04d.txt" % [n] } + +RFC_RE = %r{rfcs/rfc(\d+).*\.txt}.freeze +rule RFC_RE do |t| + require "fileutils" + FileUtils.mkpath "rfcs" + require "net/http" + t.name =~ RFC_RE + rfc_url = URI("https://www.rfc-editor.org/rfc/rfc#$1.txt") + rfc_txt = Net::HTTP.get(rfc_url) + File.write(t.name, rfc_txt) +end + +CLEAN.include "rfcs/rfc*.txt" diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/saslprep.rake ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/saslprep.rake --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/saslprep.rake 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/saslprep.rake 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative "string_prep_tables_generator" + +generator = StringPrepTablesGenerator.new + +file generator.json_filename => generator.json_deps do |t| + generator.generate_json_data_file +end + +directory "lib/net/imap/sasl" + +file "lib/net/imap/stringprep/tables.rb" => generator.rb_deps do |t| + File.write t.name, generator.stringprep_rb +end + +file "lib/net/imap/stringprep/saslprep_tables.rb" => generator.rb_deps do |t| + File.write t.name, generator.saslprep_rb +end + +GENERATED_RUBY = FileList.new( + "lib/net/imap/stringprep/tables.rb", + "lib/net/imap/stringprep/saslprep_tables.rb", +) + +CLEAN.include generator.clean_deps +CLOBBER.include GENERATED_RUBY + +task saslprep_rb: GENERATED_RUBY +task test: :saslprep_rb diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/string_prep_tables_generator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/string_prep_tables_generator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-imap-0.4.9.1/rakelib/string_prep_tables_generator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-imap-0.4.9.1/rakelib/string_prep_tables_generator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,445 @@ +# frozen_string_literal: true + +# Generator for stringprep regexps. +# +# Combines Unicode character classes with generated tables. Generated regexps +# are still used to test that the written regexps conform to the specification. +# Some tables don't match up well with any character properties available to +# ruby's regexp engine. Those use the table-generated regexps. +class StringPrepTablesGenerator + STRINGPREP_RFC_FILE = "rfcs/rfc3454.txt" + STRINGPREP_JSON_FILE = "rfcs/rfc3454-stringprep_tables.json" + + # valid UTF-8 can't contain these codepoints + # checking for them anyway, using /\p{Cs}/ ;) + SURROGATES_RANGE = 0xD800..0xDFFF + + attr_reader :json_filename, :rfc_filename + + def initialize(rfc_filename: STRINGPREP_RFC_FILE, + json_filename: STRINGPREP_JSON_FILE) + @rfc_filename = rfc_filename + @json_filename = json_filename + end + + # for rake deps + def json_deps; Rake::FileList.new __FILE__, STRINGPREP_RFC_FILE end + def rb_deps; Rake::FileList.new __FILE__, STRINGPREP_JSON_FILE end + def clean_deps; Rake::FileList.new STRINGPREP_JSON_FILE end + + def generate_json_data_file + require "json" + rfc_filename + .then(&File.method(:read)) + .then(&method(:parse_rfc_text)) + .then(&JSON.method(:pretty_generate)) + .then {|data| File.write json_filename, data } + end + + def tables; @tables ||= load_tables_and_titles_from_json!.first end + def titles; @titles ||= load_tables_and_titles_from_json!.last end + def ranges; @ranges ||= tables.transform_values(&method(:to_ranges)) end + def arrays; @arrays ||= ranges.transform_values{|t| t.flat_map(&:to_a) } end + def sets; @sets ||= arrays.transform_values(&:to_set) end + def regexps; @regexps ||= arrays.transform_values(&method(:to_regexp)) end + def asgn_regexps; @asgn_regexps || asgn_regexps! end + + def merged_tables_regex(*table_names, negate: false) + table_names + .flat_map(&arrays.method(:fetch)) + .then {|array| to_regexp(array, negate: negate) } + end + + def regexp_for(*names, negate: false) + asgn_regexps[[*names, negate]] ||= merged_tables_regex(*names, negate: negate) + end + + def stringprep_rb + <<~RUBY + # frozen_string_literal: true + + #-- + # This file is generated from RFC3454, by rake. Don't edit directly. + #++ + + module Net::IMAP::StringPrep + + module Tables + + #{asgn_table "A.1"} + + #{asgn_table "B.1"} + + #{asgn_table "B.2"} + + #{asgn_table "B.3"} + + #{asgn_mapping "B.1", ""} + + #{asgn_mapping "B.2"} + + #{asgn_mapping "B.3"} + + #{asgn_table "C.1.1"} + + #{asgn_table "C.1.2"} + + #{asgn_table "C.2.1"} + + #{asgn_table "C.2.2"} + + #{asgn_table "C.3"} + + #{asgn_table "C.4"} + + #{asgn_table "C.5"} + + #{asgn_table "C.6"} + + #{asgn_table "C.7"} + + #{asgn_table "C.8"} + + #{asgn_table "C.9"} + + #{asgn_table "D.1"} + + # Used to check req3 of bidirectional checks + #{asgn_table "D.1", negate: true} + + #{asgn_table "D.2"} + + BIDI_DESC_REQ2 = "A string with RandALCat characters must not contain LCat characters." + + # Bidirectional Characters [StringPrep, §6], Requirement 2 + # >>> + # If a string contains any RandALCat character, the string MUST NOT + # contain any LCat character. + BIDI_FAILS_REQ2 = #{bidi_fails_req2.inspect}.freeze + + BIDI_DESC_REQ3 = "A string with RandALCat characters must start and end with RandALCat characters." + + # Bidirectional Characters [StringPrep, §6], Requirement 3 + # >>> + # If a string contains any RandALCat character, a RandALCat + # character MUST be the first character of the string, and a + # RandALCat character MUST be the last character of the string. + BIDI_FAILS_REQ3 = #{bidi_fails_req3.inspect}.freeze + + # Bidirectional Characters [StringPrep, §6] + BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze + + # Names of each codepoint table in the RFC-3454 appendices + TITLES = { + #{table_titles_rb} + }.freeze + + # Regexps matching each codepoint table in the RFC-3454 appendices + REGEXPS = { + #{table_regexps_rb} + }.freeze + + MAPPINGS = { + "B.1" => [IN_B_1, MAP_B_1].freeze, + "B.2" => [IN_B_2, MAP_B_2].freeze, + "B.3" => [IN_B_3, MAP_B_3].freeze, + }.freeze + + end + end + RUBY + end + + def table_titles_rb(indent = 3) + titles + .map{|t| "%p => %p," % t } + .join("\n#{" "*indent}") + end + + def table_regexps_rb(indent = 3) + asgn_regexps # => { ["A.1", false] => regexp, ... } + .reject {|(_, n), _| n } + .map {|(t, _), _| "%p => %s," % [t, regexp_const_name(t)] } + .join("\n#{" "*indent}") + end + + def saslprep_rb + <<~RUBY + # frozen_string_literal: true + + #-- + # This file is generated from RFC3454, by rake. Don't edit directly. + #++ + + module Net::IMAP::StringPrep + + module SASLprep + + # RFC4013 §2.1 Mapping - mapped to space + # >>> + # non-ASCII space characters (\\StringPrep\\[\\"C.1.2\\"]) that can + # be mapped to SPACE (U+0020) + # + # Equal to \\StringPrep\\[\\"C.1.2\\"]. + # Redefined here to avoid loading StringPrep::Tables unless necessary. + MAP_TO_SPACE = #{regex_str "C.1.2"} + + # RFC4013 §2.1 Mapping - mapped to nothing + # >>> + # the "commonly mapped to nothing" characters + # (\\StringPrep\\[\\"B.1\\"]) that can be mapped to nothing. + # + # Equal to \\StringPrep\\[\\"B.1\\"]. + # Redefined here to avoid loading StringPrep::Tables unless necessary. + MAP_TO_NOTHING = #{regex_str "B.1"} + + # RFC4013 §2.3 Prohibited Output + # >>> + # * Non-ASCII space characters — \\StringPrep\\[\\"C.1.2\\"] + # * ASCII control characters — \\StringPrep\\[\\"C.2.1\\"] + # * Non-ASCII control characters — \\StringPrep\\[\\"C.2.2\\"] + # * Private Use characters — \\StringPrep\\[\\"C.3\\"] + # * Non-character code points — \\StringPrep\\[\\"C.4\\"] + # * Surrogate code points — \\StringPrep\\[\\"C.5\\"] + # * Inappropriate for plain text characters — \\StringPrep\\[\\"C.6\\"] + # * Inappropriate for canonical representation characters — \\StringPrep\\[\\"C.7\\"] + # * Change display properties or deprecated characters — \\StringPrep\\[\\"C.8\\"] + # * Tagging characters — \\StringPrep\\[\\"C.9\\"] + TABLES_PROHIBITED = #{SASL_TABLES_PROHIBITED.inspect}.freeze + + # Adds unassigned (by Unicode 3.2) codepoints to TABLES_PROHIBITED. + # + # RFC4013 §2.5 Unassigned Code Points + # >>> + # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its + # list of unassigned code points. + TABLES_PROHIBITED_STORED = ["A.1", *TABLES_PROHIBITED].freeze + + # A Regexp matching codepoints prohibited by RFC4013 §2.3. + # + # This combines all of the TABLES_PROHIBITED tables. + PROHIBITED_OUTPUT = #{regex_str(*SASL_TABLES_PROHIBITED)} + + # RFC4013 §2.5 Unassigned Code Points + # >>> + # This profile specifies the \\StringPrep\\[\\"A.1\\"] table as its + # list of unassigned code points. + # + # Equal to \\StringPrep\\[\\"A.1\\"]. + # Redefined here to avoid loading StringPrep::Tables unless necessary. + UNASSIGNED = #{regex_str "A.1"} + + # A Regexp matching codepoints prohibited by RFC4013 §2.3 and §2.5. + # + # This combines PROHIBITED_OUTPUT and UNASSIGNED. + PROHIBITED_OUTPUT_STORED = Regexp.union( + UNASSIGNED, PROHIBITED_OUTPUT + ).freeze + + # Bidirectional Characters [StringPrep, §6] + # + # A Regexp for strings that don't satisfy StringPrep's Bidirectional + # Characters rules. + # + # Equal to StringPrep::Tables::BIDI_FAILURE. + # Redefined here to avoid loading StringPrep::Tables unless necessary. + BIDI_FAILURE = #{bidi_failure_regexp.inspect}.freeze + + # A Regexp matching strings prohibited by RFC4013 §2.3 and §2.4. + # + # This combines PROHIBITED_OUTPUT and BIDI_FAILURE. + PROHIBITED = Regexp.union( + PROHIBITED_OUTPUT, BIDI_FAILURE, + ) + + # A Regexp matching strings prohibited by RFC4013 §2.3, §2.4, and §2.5. + # + # This combines PROHIBITED_OUTPUT_STORED and BIDI_FAILURE. + PROHIBITED_STORED = Regexp.union( + PROHIBITED_OUTPUT_STORED, BIDI_FAILURE, + ) + + end + end + RUBY + end + + private + + def parse_rfc_text(rfc3454_text) + titles = {} + tables, = rfc3454_text + .lines + .each_with_object([]) {|line, acc| + current, table = acc.last + case line + when /^([A-D]\.[1-9](?:\.[1-9])?) (.*)/ + titles[$1] = $2 + when /^ {3}-{5} Start Table (\S*)/ + acc << [$1, []] + when /^ {3}-{5} End Table / + acc << [nil, nil] + when /^ {3}([0-9A-F]+); ([ 0-9A-F]*)(?:;[^;]*)$/ # mapping tables + table << [$1, $2.split(/ +/)] if current + when /^ {3}([-0-9A-F]+)(?:;[^;]*)?$/ # regular tables + table << $1 if current + when /^ {3}(.*)/ + raise "expected to match %p" % $1 if current + end + } + .to_h.compact + .transform_values {|t| t.first.size == 2 ? t.to_h : t } + tables["titles"] = titles + tables + end + + def load_tables_and_titles_from_json! + require "json" + @tables = json_filename + .then(&File.method(:read)) + .then(&JSON.method(:parse)) + @titles = @tables.delete "titles" + [@tables, @titles] + end + + def to_ranges(table) + (table.is_a?(Hash) ? table.keys : table) + .map{|range| range.split(?-).map{|cp| Integer cp, 16} } + .map{|s,e| s..(e || s)} + end + + # TODO: DRY with unicode_normalize + def to_map(table) + table = table.to_hash + .transform_keys { Integer _1, 16 } + .transform_keys { [_1].pack("U*") } + .transform_values {|cps| cps.map { Integer _1, 16 } } + .transform_values { _1.pack("U*") } + end + + # Starting from a codepoints array (rather than ranges) to deduplicate merged + # tables. + def to_regexp(codepoints, negate: false) + codepoints + .grep_v(SURROGATES_RANGE) # remove surrogate codepoints from C.5 and D.2 + .uniq + .sort + .chunk_while {|cp1,cp2| cp1 + 1 == cp2 } # find contiguous chunks + .map {|chunk| chunk.map{|cp| "%04x" % cp } } # convert to hex strings + .partition {|chunk| chunk[1] } # ranges vs singles + .then {|ranges, singles| + singles.flatten! + [ + negate ? "^" : "", + singles.flatten.any? ? "\\u{%s}" % singles.join(" ") : "", + ranges.map {|r| "\\u{%s}-\\u{%s}" % [r.first, r.last] }.join, + codepoints.any?(SURROGATES_RANGE) ? "\\p{Cs}" : "", # not necessary :) + ].join + } + .then {|char_class| Regexp.new "[#{char_class}]" } + end + + def asgn_regexps! + @asgn_regexps = {} + # preset the regexp for each table + asgn_regex "A.1", /\p{^AGE=3.2}/ + # If ruby supported all unicode properties (i.e. line break = word joiner): + # /[\u{00ad 034f 1806}\p{join_c}\p{VS}\p{lb=WJ}&&\p{age=3.2}]/ + asgn_table "B.1" + asgn_table "B.2" + asgn_table "B.3" + asgn_regex "C.1.1", / / + asgn_regex "C.1.2", /[\u200b\p{Zs}&&[^ ]]/ + asgn_regex "C.2.1", /[\x00-\x1f\x7f]/ + # C.2.2 is a union: + # Cc + Cf (as defined by Unicode 3.2) + Zl + Zp + 0xfffc + # - any codepoints covered by C.2.1 or C.8 or C.9 + # + # But modern Unicode properties are significantly different, so it's better + # to just load the table definition. + asgn_table "C.2.2" + asgn_regex "C.3", /\p{private use}/ + asgn_regex "C.4", /\p{noncharacter code point}/ + asgn_regex "C.5", /\p{surrogate}/ + asgn_regex "C.6", /[\p{in specials}&&\p{AGE=3.2}&&\p{^NChar}]/ + asgn_regex "C.7", /[\p{in ideographic description characters}&&\p{AGE=3.2}]/ + # C.8 is a union of \p{Bidi Control} and Unicode 3.2 properties. But those properties + # have changed for modern Unicode, and thus for modern ruby's regexp + # character properties. It's better to just load the table definition. + asgn_table "C.8" + asgn_regex "C.9", /[\p{in Tags}&&\p{AGE=3.2}]/ + # Unfortunately, ruby doesn't (currently) support /[\p{Bidi + # Class=R}\p{bc=AL}]/. On the other hand, StringPrep (based on Unicode 3.2) + # might not be a good match for the modern (14.0) property value anyway. + asgn_table "D.1" + asgn_table "D.1", negate: true # used by BIDI_FAILS_REQ3 + asgn_table "D.2" + @asgn_regexps + end + + def regex_str(*names, negate: false) + "%p.freeze" % regexp_for(*names, negate: negate) + end + + def asgn_table(name, negate: false) + asgn_regex(name, regexp_for(name, negate: negate), negate: negate) + end + + def asgn_mapping(name, replacement = to_map(tables[name])) + cname = name.tr(?., ?_).upcase + "# Replacements for %s\n%s%s = %p.freeze" % [ + "IN_#{name}", " " * 2, "MAP_#{cname}", replacement, + ] + end + + def regexp_const_desc(name, negate: false) + if negate then "Matches the negation of the %s table" % [name] + else %q{%s \\StringPrep\\[\\"%s\\"]} % [titles.fetch(name), name] + end + end + + def regexp_const_name(table_name, negate: false) + "IN_%s%s" % [table_name.tr(".", "_"), negate ? "_NEGATED" : ""] + end + + def asgn_regex(name, regexp, negate: false) + asgn_regexps[[name, negate]] = regexp + "# %s\n%s%s = %p.freeze" % [ + regexp_const_desc(name, negate: negate), " " * 4, + regexp_const_name(name, negate: negate), + regexp, + ] + end + + def bidi_R_AL ; regexp_for "D.1" end + def bidi_not_R_AL ; regexp_for "D.1", negate: true end + def bidi_L ; regexp_for "D.2" end + + def bidi_fails_req2 + Regexp.union( + /#{bidi_R_AL}.*?#{bidi_L}/mu, # RandALCat followed by LCat + /#{bidi_L}.*?#{bidi_R_AL}/mu, # RandALCat preceded by LCat + ) + end + + def bidi_fails_req3 + # contains RandALCat: + Regexp.union( + /\A#{bidi_not_R_AL}.*?#{bidi_R_AL}/mu, # but doesn't start with RandALCat + /#{bidi_R_AL}.*?#{bidi_not_R_AL}\z/mu, # but doesn't end with RandALCat + ) + end + + def bidi_failure_regexp + Regexp.union(bidi_fails_req2, bidi_fails_req3) + end + + SASL_TABLES_PROHIBITED = %w[ + C.1.2 C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.7 C.8 C.9 + ].freeze + + SASL_TABLES_PROHIBITED_STORED = %w[ + A.1 C.1.2 C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.7 C.8 C.9 + ].freeze + +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/LICENSE.txt ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/LICENSE.txt --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/LICENSE.txt 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/NEWS.md ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/NEWS.md --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/NEWS.md 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/NEWS.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,114 +0,0 @@ -# NEWS - -## Version 0.4.0 (2023-09-20) - -### Improvements - -* add Net::SMTP::Authenticator class and auth_* methods are separated from the Net::SMTP class. - This allows you to add a new authentication method to Net::SMTP. - Create a class with an `auth` method that inherits Net::SMTP::Authenticator. - The `auth` method has two arguments, `user` and `secret`. - Send an instruction to the SMTP server by using the `continue` or `finish` method. - For more information, see lib/net/smtp/auto _*.rb. -* Add SMTPUTF8 support - -### Fixes - -* Revert "Replace Timeout.timeout with socket timeout" -* Fixed issue sending emails to unaffected recipients on 53x error - -### Others - -* Removed unnecessary Subversion keywords - -## Version 0.3.3 (2022-10-29) - -* No timeout library required -* Make the digest library optional - -## Version 0.3.2 (2022-09-28) - -* Make exception API compatible with what Ruby expects - -## Version 0.3.1 (2021-12-12) - -### Improvements - -* add Net::SMTP::Address. -* add Net::SMTP#capable? and Net::SMTP#capabilities. -* add Net::SMTP#tls_verify, Net::SMTP#tls_hostname, Net::SMTP#ssl_context_params - -## Version 0.3.0 (2021-10-14) - -### Improvements - -* Add `tls`, `starttls` keyword arguments. - ```ruby - # always use TLS connection for port 465. - Net::SMTP.start(hostname, 465, tls: true) - - # do not use starttls for localhost - Net::SMTP.start('localhost', starttls: false) - ``` - -### Incompatible changes - -* The tls_* paramter has been moved from start() to initialize(). - -## Version 0.2.2 (2021-10-09) - -* Add `response` to SMTPError exceptions. -* `Net::SMTP.start()` and `#start()` accepts `ssl_context_params` keyword argument. -* Replace `Timeout.timeout` with socket timeout. -* Remove needless files from gem. -* Add dependency on digest, timeout. - -## Version 0.2.1 (2020-11-18) - -### Fixes - -* Update the license for the default gems to dual licenses. -* Add dependency for net-protocol. - -## Version 0.2.0 (2020-11-15) - -### Incompatible changes - -* Verify the server's certificate by default. - If you don't want verification, specify `start(tls_verify: false)`. - - -* Use STARTTLS by default if possible. - If you don't want starttls, specify: - ``` - smtp = Net::SMTP.new(hostname, port) - smtp.disable_starttls - smtp.start do |s| - s.send_message .... - end - ``` - - -### Improvements - -* Net::SMTP.start and Net::SMTP#start arguments are keyword arguments. - ``` - start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... } - ``` - `password` is an alias of `secret`. - - -* Add `tls_hostname` parameter to `start()`. - If you want to use a different hostname than the certificate for the connection, you can specify the certificate hostname with `tls_hostname`. - - -* Add SNI support to net/smtp - -### Fixes - -* enable_starttls before disable_tls causes an error. -* TLS should not check the hostname when verify_mode is disabled. - -## Version 0.1.0 (2019-12-03) - -This is the first release of net-smtp gem. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/README.md ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/README.md --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/README.md 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,97 +0,0 @@ -# Net::SMTP - -This library provides functionality to send internet mail via SMTP, the Simple Mail Transfer Protocol. - -For details of SMTP itself, see [RFC2821](http://www.ietf.org/rfc/rfc2821.txt). - -## Installation - -Add this line to your application's Gemfile: - -```ruby -gem 'net-smtp' -``` - -And then execute: - - $ bundle - -Or install it yourself as: - - $ gem install net-smtp - -## Usage - -### Sending Messages - -You must open a connection to an SMTP server before sending messages. -The first argument is the address of your SMTP server, and the second -argument is the port number. Using SMTP.start with a block is the simplest -way to do this. This way, the SMTP connection is closed automatically -after the block is executed. - -```ruby -require 'net/smtp' -Net::SMTP.start('your.smtp.server', 25) do |smtp| - # Use the SMTP object smtp only in this block. -end -``` - -Replace 'your.smtp.server' with your SMTP server. Normally -your system manager or internet provider supplies a server -for you. - -Then you can send messages. - -```ruby -msgstr = < -To: Destination Address -Subject: test message -Date: Sat, 23 Jun 2001 16:26:43 +0900 -Message-Id: - -This is a test message. -END_OF_MESSAGE - -require 'net/smtp' -Net::SMTP.start('your.smtp.server', 25) do |smtp| - smtp.send_message msgstr, - 'your@mail.address', - 'his_address@example.com' -end -``` - -### Closing the Session - -You MUST close the SMTP session after sending messages, by calling -the #finish method: - -```ruby -# using SMTP#finish -smtp = Net::SMTP.start('your.smtp.server', 25) -smtp.send_message msgstr, 'from@address', 'to@address' -smtp.finish -``` - -You can also use the block form of SMTP.start/SMTP#start. This closes -the SMTP session automatically: - -```ruby -# using block form of SMTP.start -Net::SMTP.start('your.smtp.server', 25) do |smtp| - smtp.send_message msgstr, 'from@address', 'to@address' -end -``` - -I strongly recommend this scheme. This form is simpler and more robust. - -## Development - -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. - -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). - -## Contributing - -Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-smtp. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_cram_md5.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_cram_md5.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_cram_md5.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_cram_md5.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -unless defined? OpenSSL - begin - require 'digest/md5' - rescue LoadError - end -end - -class Net::SMTP - class AuthCramMD5 < Net::SMTP::Authenticator - auth_type :cram_md5 - - def auth(user, secret) - challenge = continue('AUTH CRAM-MD5') - crammed = cram_md5_response(secret, challenge.unpack1('m')) - finish(base64_encode("#{user} #{crammed}")) - end - - IMASK = 0x36 - OMASK = 0x5c - - # CRAM-MD5: [RFC2195] - def cram_md5_response(secret, challenge) - tmp = digest_class::MD5.digest(cram_secret(secret, IMASK) + challenge) - digest_class::MD5.hexdigest(cram_secret(secret, OMASK) + tmp) - end - - CRAM_BUFSIZE = 64 - - def cram_secret(secret, mask) - secret = digest_class::MD5.digest(secret) if secret.size > CRAM_BUFSIZE - buf = secret.ljust(CRAM_BUFSIZE, "\0") - 0.upto(buf.size - 1) do |i| - buf[i] = (buf[i].ord ^ mask).chr - end - buf - end - - def digest_class - @digest_class ||= if defined?(OpenSSL::Digest) - OpenSSL::Digest - elsif defined?(::Digest) - ::Digest - else - raise '"openssl" or "digest" library is required' - end - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_login.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_login.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_login.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_login.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -class Net::SMTP - class AuthLogin < Net::SMTP::Authenticator - auth_type :login - - def auth(user, secret) - continue('AUTH LOGIN') - continue(base64_encode(user)) - finish(base64_encode(secret)) - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_plain.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_plain.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_plain.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/auth_plain.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -class Net::SMTP - class AuthPlain < Net::SMTP::Authenticator - auth_type :plain - - def auth(user, secret) - finish('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}")) - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/authenticator.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp/authenticator.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,46 +0,0 @@ -module Net - class SMTP - class Authenticator - def self.auth_classes - @classes ||= {} - end - - def self.auth_type(type) - Authenticator.auth_classes[type] = self - end - - def self.auth_class(type) - Authenticator.auth_classes[type.intern] - end - - attr_reader :smtp - - def initialize(smtp) - @smtp = smtp - end - - # @param arg [String] message to server - # @return [String] message from server - def continue(arg) - res = smtp.get_response arg - raise res.exception_class.new(res) unless res.continue? - res.string.split[1] - end - - # @param arg [String] message to server - # @return [Net::SMTP::Response] response from server - def finish(arg) - res = smtp.get_response arg - raise SMTPAuthenticationError.new(res) unless res.success? - res - end - - # @param str [String] - # @return [String] Base64 encoded string - def base64_encode(str) - # expects "str" may not become too long - [str].pack('m0') - end - end - end -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0/lib/net/smtp.rb 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0/lib/net/smtp.rb 1970-01-01 00:00:00.000000000 +0000 @@ -1,1139 +0,0 @@ -# frozen_string_literal: true - -# = net/smtp.rb -# -# Copyright (c) 1999-2007 Yukihiro Matsumoto. -# -# Copyright (c) 1999-2007 Minero Aoki. -# -# Written & maintained by Minero Aoki . -# -# Documented by William Webber and Minero Aoki. -# -# This program is free software. You can re-distribute and/or -# modify this program under the same terms as Ruby itself. -# -# See Net::SMTP for documentation. -# - -require 'net/protocol' -begin - require 'openssl' -rescue LoadError -end - -module Net - # Module mixed in to all SMTP error classes - module SMTPError - # This *class* is a module for backward compatibility. - # In later release, this module becomes a class. - - attr_reader :response - - def initialize(response, message: nil) - if response.is_a?(::Net::SMTP::Response) - @response = response - @message = message - else - @response = nil - @message = message || response - end - end - - def message - @message || response.message - end - end - - # Represents an SMTP authentication error. - class SMTPAuthenticationError < ProtoAuthError - include SMTPError - end - - # Represents SMTP error code 4xx, a temporary error. - class SMTPServerBusy < ProtoServerError - include SMTPError - end - - # Represents an SMTP command syntax error (error code 500) - class SMTPSyntaxError < ProtoSyntaxError - include SMTPError - end - - # Represents a fatal SMTP error (error code 5xx, except for 500) - class SMTPFatalError < ProtoFatalError - include SMTPError - end - - # Unexpected reply code returned from server. - class SMTPUnknownError < ProtoUnknownError - include SMTPError - end - - # Command is not supported on server. - class SMTPUnsupportedCommand < ProtocolError - include SMTPError - end - - # - # == What is This Library? - # - # This library provides functionality to send internet - # mail via SMTP, the Simple Mail Transfer Protocol. For details of - # SMTP itself, see [RFC5321] (http://www.ietf.org/rfc/rfc5321.txt). - # This library also implements SMTP authentication, which is often - # necessary for message composers to submit messages to their - # outgoing SMTP server, see - # [RFC6409](http://www.ietf.org/rfc/rfc6503.txt), - # and [SMTPUTF8](http://www.ietf.org/rfc/rfc6531.txt), which is - # necessary to send messages to/from addresses containing characters - # outside the ASCII range. - # - # == What is This Library NOT? - # - # This library does NOT provide functions to compose internet mails. - # You must create them by yourself. If you want better mail support, - # try RubyMail or TMail or search for alternatives in - # {RubyGems.org}[https://rubygems.org/] or {The Ruby - # Toolbox}[https://www.ruby-toolbox.com/]. - # - # FYI: the official specification on internet mail is: [RFC5322] (http://www.ietf.org/rfc/rfc5322.txt). - # - # == Examples - # - # === Sending Messages - # - # You must open a connection to an SMTP server before sending messages. - # The first argument is the address of your SMTP server, and the second - # argument is the port number. Using SMTP.start with a block is the simplest - # way to do this. This way, the SMTP connection is closed automatically - # after the block is executed. - # - # require 'net/smtp' - # Net::SMTP.start('your.smtp.server', 25) do |smtp| - # # Use the SMTP object smtp only in this block. - # end - # - # Replace 'your.smtp.server' with your SMTP server. Normally - # your system manager or internet provider supplies a server - # for you. - # - # Then you can send messages. - # - # msgstr = < - # To: Destination Address - # Subject: test message - # Date: Sat, 23 Jun 2001 16:26:43 +0900 - # Message-Id: - # - # This is a test message. - # END_OF_MESSAGE - # - # require 'net/smtp' - # Net::SMTP.start('your.smtp.server', 25) do |smtp| - # smtp.send_message msgstr, - # 'your@mail.address', - # 'his_address@example.com' - # end - # - # === Closing the Session - # - # You MUST close the SMTP session after sending messages, by calling - # the #finish method: - # - # # using SMTP#finish - # smtp = Net::SMTP.start('your.smtp.server', 25) - # smtp.send_message msgstr, 'from@address', 'to@address' - # smtp.finish - # - # You can also use the block form of SMTP.start/SMTP#start. This closes - # the SMTP session automatically: - # - # # using block form of SMTP.start - # Net::SMTP.start('your.smtp.server', 25) do |smtp| - # smtp.send_message msgstr, 'from@address', 'to@address' - # end - # - # I strongly recommend this scheme. This form is simpler and more robust. - # - # === HELO domain - # - # In almost all situations, you must provide a third argument - # to SMTP.start/SMTP#start. This is the domain name which you are on - # (the host to send mail from). It is called the "HELO domain". - # The SMTP server will judge whether it should send or reject - # the SMTP session by inspecting the HELO domain. - # - # Net::SMTP.start('your.smtp.server', 25 - # helo: 'mail.from.domain') { |smtp| ... } - # - # === SMTP Authentication - # - # The Net::SMTP class supports three authentication schemes; - # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554]) - # To use SMTP authentication, pass extra arguments to - # SMTP.start/SMTP#start. - # - # # PLAIN - # Net::SMTP.start('your.smtp.server', 25 - # user: 'Your Account', secret: 'Your Password', authtype: :plain) - # # LOGIN - # Net::SMTP.start('your.smtp.server', 25 - # user: 'Your Account', secret: 'Your Password', authtype: :login) - # - # # CRAM MD5 - # Net::SMTP.start('your.smtp.server', 25 - # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5) - # - class SMTP < Protocol - VERSION = "0.4.0" - - # The default SMTP port number, 25. - def SMTP.default_port - 25 - end - - # The default mail submission port number, 587. - def SMTP.default_submission_port - 587 - end - - # The default SMTPS port number, 465. - def SMTP.default_tls_port - 465 - end - - class << self - alias default_ssl_port default_tls_port - end - - def SMTP.default_ssl_context(ssl_context_params = nil) - context = OpenSSL::SSL::SSLContext.new - context.set_params(ssl_context_params || {}) - context - end - - # - # Creates a new Net::SMTP object. - # - # +address+ is the hostname or ip address of your SMTP - # server. +port+ is the port to connect to; it defaults to - # port 25. - # - # If +tls+ is true, enable TLS. The default is false. - # If +starttls+ is :always, enable STARTTLS, if +:auto+, use STARTTLS when the server supports it, - # if false, disable STARTTLS. - # - # If +tls_verify+ is true, verify the server's certificate. The default is true. - # If the hostname in the server certificate is different from +address+, - # it can be specified with +tls_hostname+. - # - # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to - # +OpenSSL::SSL::SSLContext#set_params+ - # - # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+. - # This method does not open the TCP connection. You can use - # SMTP.start instead of SMTP.new if you want to do everything - # at once. Otherwise, follow SMTP.new with SMTP#start. - # - def initialize(address, port = nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) - @address = address - @port = (port || SMTP.default_port) - @esmtp = true - @capabilities = nil - @socket = nil - @started = false - @open_timeout = 30 - @read_timeout = 60 - @error_occurred = false - @debug_output = nil - @tls = tls - @starttls = starttls - @ssl_context_tls = nil - @ssl_context_starttls = nil - @tls_verify = tls_verify - @tls_hostname = tls_hostname - @ssl_context_params = ssl_context_params - end - - # If +true+, verify th server's certificate. - attr_accessor :tls_verify - - # The hostname for verifying hostname in the server certificatate. - attr_accessor :tls_hostname - - # Hash for additional SSLContext parameters. - attr_accessor :ssl_context_params - - # Provide human-readable stringification of class state. - def inspect - "#<#{self.class} #{@address}:#{@port} started=#{@started}>" - end - - # - # Set whether to use ESMTP or not. This should be done before - # calling #start. Note that if #start is called in ESMTP mode, - # and the connection fails due to a ProtocolError, the SMTP - # object will automatically switch to plain SMTP mode and - # retry (but not vice versa). - # - attr_accessor :esmtp - - # +true+ if the SMTP object uses ESMTP (which it does by default). - alias esmtp? esmtp - - # true if server advertises STARTTLS. - # You cannot get valid value before opening SMTP session. - def capable_starttls? - capable?('STARTTLS') - end - - # true if the EHLO response contains +key+. - def capable?(key) - return nil unless @capabilities - @capabilities[key] ? true : false - end - - # The server capabilities by EHLO response - attr_reader :capabilities - - # true if server advertises AUTH PLAIN. - # You cannot get valid value before opening SMTP session. - def capable_plain_auth? - auth_capable?('PLAIN') - end - - # true if server advertises AUTH LOGIN. - # You cannot get valid value before opening SMTP session. - def capable_login_auth? - auth_capable?('LOGIN') - end - - # true if server advertises AUTH CRAM-MD5. - # You cannot get valid value before opening SMTP session. - def capable_cram_md5_auth? - auth_capable?('CRAM-MD5') - end - - def auth_capable?(type) - return nil unless @capabilities - return false unless @capabilities['AUTH'] - @capabilities['AUTH'].include?(type) - end - private :auth_capable? - - # Returns supported authentication methods on this server. - # You cannot get valid value before opening SMTP session. - def capable_auth_types - return [] unless @capabilities - return [] unless @capabilities['AUTH'] - @capabilities['AUTH'] - end - - # true if this object uses SMTP/TLS (SMTPS). - def tls? - @tls - end - - alias ssl? tls? - - # Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for - # this object. Must be called before the connection is established - # to have any effect. +context+ is a OpenSSL::SSL::SSLContext object. - def enable_tls(context = nil) - raise 'openssl library not installed' unless defined?(OpenSSL::VERSION) - raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls == :always - @tls = true - @ssl_context_tls = context - end - - alias enable_ssl enable_tls - - # Disables SMTP/TLS for this object. Must be called before the - # connection is established to have any effect. - def disable_tls - @tls = false - @ssl_context_tls = nil - end - - alias disable_ssl disable_tls - - # Returns truth value if this object uses STARTTLS. - # If this object always uses STARTTLS, returns :always. - # If this object uses STARTTLS when the server support TLS, returns :auto. - def starttls? - @starttls - end - - # true if this object uses STARTTLS. - def starttls_always? - @starttls == :always - end - - # true if this object uses STARTTLS when server advertises STARTTLS. - def starttls_auto? - @starttls == :auto - end - - # Enables SMTP/TLS (STARTTLS) for this object. - # +context+ is a OpenSSL::SSL::SSLContext object. - def enable_starttls(context = nil) - raise 'openssl library not installed' unless defined?(OpenSSL::VERSION) - raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls - @starttls = :always - @ssl_context_starttls = context - end - - # Enables SMTP/TLS (STARTTLS) for this object if server accepts. - # +context+ is a OpenSSL::SSL::SSLContext object. - def enable_starttls_auto(context = nil) - raise 'openssl library not installed' unless defined?(OpenSSL::VERSION) - raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls - @starttls = :auto - @ssl_context_starttls = context - end - - # Disables SMTP/TLS (STARTTLS) for this object. Must be called - # before the connection is established to have any effect. - def disable_starttls - @starttls = false - @ssl_context_starttls = nil - end - - # The address of the SMTP server to connect to. - attr_reader :address - - # The port number of the SMTP server to connect to. - attr_reader :port - - # Seconds to wait while attempting to open a connection. - # If the connection cannot be opened within this time, a - # Net::OpenTimeout is raised. The default value is 30 seconds. - attr_accessor :open_timeout - - # Seconds to wait while reading one block (by one read(2) call). - # If the read(2) call does not complete within this time, a - # Net::ReadTimeout is raised. The default value is 60 seconds. - attr_reader :read_timeout - - # Set the number of seconds to wait until timing-out a read(2) - # call. - def read_timeout=(sec) - @socket.read_timeout = sec if @socket - @read_timeout = sec - end - - # - # WARNING: This method causes serious security holes. - # Use this method for only debugging. - # - # Set an output stream for debug logging. - # You must call this before #start. - # - # # example - # smtp = Net::SMTP.new(addr, port) - # smtp.set_debug_output $stderr - # smtp.start do |smtp| - # .... - # end - # - def debug_output=(arg) - @debug_output = arg - end - - alias set_debug_output debug_output= - - # - # SMTP session control - # - - # - # :call-seq: - # start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } - # start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } - # - # Creates a new Net::SMTP object and connects to the server. - # - # This method is equivalent to: - # - # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname, ssl_context_params: nil) - # - # === Example - # - # Net::SMTP.start('your.smtp.server') do |smtp| - # smtp.send_message msgstr, 'from@example.com', ['dest@example.com'] - # end - # - # === Block Usage - # - # If called with a block, the newly-opened Net::SMTP object is yielded - # to the block, and automatically closed when the block finishes. If called - # without a block, the newly-opened Net::SMTP object is returned to - # the caller, and it is the caller's responsibility to close it when - # finished. - # - # === Parameters - # - # +address+ is the hostname or ip address of your smtp server. - # - # +port+ is the port to connect to; it defaults to port 25. - # - # +helo+ is the _HELO_ _domain_ provided by the client to the - # server (see overview comments); it defaults to 'localhost'. - # - # The remaining arguments are used for SMTP authentication, if required - # or desired. +user+ is the account name; +secret+ is your password - # or other authentication token; and +authtype+ is the authentication - # type, one of :plain, :login, or :cram_md5. See the discussion of - # SMTP Authentication in the overview notes. - # - # If +tls+ is true, enable TLS. The default is false. - # If +starttls+ is :always, enable STARTTLS, if +:auto+, use STARTTLS when the server supports it, - # if false, disable STARTTLS. - # - # If +tls_verify+ is true, verify the server's certificate. The default is true. - # If the hostname in the server certificate is different from +address+, - # it can be specified with +tls_hostname+. - # - # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to - # +OpenSSL::SSL::SSLContext#set_params+ - # - # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+. - # - # === Errors - # - # This method may raise: - # - # * Net::SMTPAuthenticationError - # * Net::SMTPServerBusy - # * Net::SMTPSyntaxError - # * Net::SMTPFatalError - # * Net::SMTPUnknownError - # * Net::OpenTimeout - # * Net::ReadTimeout - # * IOError - # - def SMTP.start(address, port = nil, *args, helo: nil, - user: nil, secret: nil, password: nil, authtype: nil, - tls: false, starttls: :auto, - tls_verify: true, tls_hostname: nil, ssl_context_params: nil, - &block) - raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 1..6)" if args.size > 4 - helo ||= args[0] || 'localhost' - user ||= args[1] - secret ||= password || args[2] - authtype ||= args[3] - new(address, port, tls: tls, starttls: starttls, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params).start(helo: helo, user: user, secret: secret, authtype: authtype, &block) - end - - # +true+ if the SMTP session has been started. - def started? - @started - end - - # - # :call-seq: - # start(helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... } - # start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } - # - # Opens a TCP connection and starts the SMTP session. - # - # === Parameters - # - # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see - # the discussion in the overview notes. - # - # If both of +user+ and +secret+ are given, SMTP authentication - # will be attempted using the AUTH command. +authtype+ specifies - # the type of authentication to attempt; it must be one of - # :login, :plain, and :cram_md5. See the notes on SMTP Authentication - # in the overview. - # - # === Block Usage - # - # When this methods is called with a block, the newly-started SMTP - # object is yielded to the block, and automatically closed after - # the block call finishes. Otherwise, it is the caller's - # responsibility to close the session when finished. - # - # === Example - # - # This is very similar to the class method SMTP.start. - # - # require 'net/smtp' - # smtp = Net::SMTP.new('smtp.mail.server', 25) - # smtp.start(helo: helo_domain, user: account, secret: password, authtype: authtype) do |smtp| - # smtp.send_message msgstr, 'from@example.com', ['dest@example.com'] - # end - # - # The primary use of this method (as opposed to SMTP.start) - # is probably to set debugging (#set_debug_output) or ESMTP - # (#esmtp=), which must be done before the session is - # started. - # - # === Errors - # - # If session has already been started, an IOError will be raised. - # - # This method may raise: - # - # * Net::SMTPAuthenticationError - # * Net::SMTPServerBusy - # * Net::SMTPSyntaxError - # * Net::SMTPFatalError - # * Net::SMTPUnknownError - # * Net::OpenTimeout - # * Net::ReadTimeout - # * IOError - # - def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil) - raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..4)" if args.size > 4 - helo ||= args[0] || 'localhost' - user ||= args[1] - secret ||= password || args[2] - authtype ||= args[3] - if defined?(OpenSSL::VERSION) - ssl_context_params = @ssl_context_params || {} - unless ssl_context_params.has_key?(:verify_mode) - ssl_context_params[:verify_mode] = @tls_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE - end - if @tls && @ssl_context_tls.nil? - @ssl_context_tls = SMTP.default_ssl_context(ssl_context_params) - end - if @starttls && @ssl_context_starttls.nil? - @ssl_context_starttls = SMTP.default_ssl_context(ssl_context_params) - end - end - if block_given? - begin - do_start helo, user, secret, authtype - return yield(self) - ensure - do_finish - end - else - do_start helo, user, secret, authtype - return self - end - end - - # Finishes the SMTP session and closes TCP connection. - # Raises IOError if not started. - def finish - raise IOError, 'not yet started' unless started? - do_finish - end - - private - - def tcp_socket(address, port) - TCPSocket.open address, port - end - - def do_start(helo_domain, user, secret, authtype) - raise IOError, 'SMTP session already started' if @started - if user or secret - check_auth_method(authtype || DEFAULT_AUTH_TYPE) - check_auth_args user, secret - end - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do - tcp_socket(@address, @port) - end - logging "Connection opened: #{@address}:#{@port}" - @socket = new_internet_message_io(tls? ? tlsconnect(s, @ssl_context_tls) : s) - check_response critical { recv_response() } - do_helo helo_domain - if ! tls? and (starttls_always? or (capable_starttls? and starttls_auto?)) - unless capable_starttls? - raise SMTPUnsupportedCommand, "STARTTLS is not supported on this server" - end - starttls - @socket = new_internet_message_io(tlsconnect(s, @ssl_context_starttls)) - # helo response may be different after STARTTLS - do_helo helo_domain - end - authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user - @started = true - ensure - unless @started - # authentication failed, cancel connection. - s.close if s - @socket = nil - end - end - - def ssl_socket(socket, context) - OpenSSL::SSL::SSLSocket.new socket, context - end - - def tlsconnect(s, context) - verified = false - s = ssl_socket(s, context) - logging "TLS connection started" - s.sync_close = true - s.hostname = @tls_hostname || @address - ssl_socket_connect(s, @open_timeout) - verified = true - s - ensure - s.close unless verified - end - - def new_internet_message_io(s) - InternetMessageIO.new(s, read_timeout: @read_timeout, - debug_output: @debug_output) - end - - def do_helo(helo_domain) - res = @esmtp ? ehlo(helo_domain) : helo(helo_domain) - @capabilities = res.capabilities - rescue SMTPError - if @esmtp - @esmtp = false - @error_occurred = false - retry - end - raise - end - - def do_finish - quit if @socket and not @socket.closed? and not @error_occurred - ensure - @started = false - @error_occurred = false - @socket.close if @socket - @socket = nil - end - - def requires_smtputf8(address) - if address.kind_of? Address - !address.address.ascii_only? - else - !address.ascii_only? - end - end - - def any_require_smtputf8(addresses) - addresses.any?{ |a| requires_smtputf8(a) } - end - - # - # Message Sending - # - - public - - # - # Sends +msgstr+ as a message. Single CR ("\r") and LF ("\n") found - # in the +msgstr+, are converted into the CR LF pair. You cannot send a - # binary message with this method. +msgstr+ should include both - # the message headers and body. - # - # +from_addr+ is a String or Net::SMTP::Address representing the source mail address. - # - # +to_addr+ is a String or Net::SMTP::Address or Array of them, representing - # the destination mail address or addresses. - # - # === Example - # - # Net::SMTP.start('smtp.example.com') do |smtp| - # smtp.send_message msgstr, - # 'from@example.com', - # ['dest@example.com', 'dest2@example.com'] - # end - # - # Net::SMTP.start('smtp.example.com') do |smtp| - # smtp.send_message msgstr, - # Net::SMTP::Address.new('from@example.com', size: 12345), - # Net::SMTP::Address.new('dest@example.com', notify: :success) - # end - # - # === Errors - # - # This method may raise: - # - # * Net::SMTPServerBusy - # * Net::SMTPSyntaxError - # * Net::SMTPFatalError - # * Net::SMTPUnknownError - # * Net::ReadTimeout - # * IOError - # - def send_message(msgstr, from_addr, *to_addrs) - to_addrs.flatten! - raise IOError, 'closed session' unless @socket - from_addr = Address.new(from_addr, 'SMTPUTF8') if any_require_smtputf8(to_addrs) && capable?('SMTPUTF8') - mailfrom from_addr - rcptto_list(to_addrs) {data msgstr} - end - - alias send_mail send_message - alias sendmail send_message # obsolete - - # - # Opens a message writer stream and gives it to the block. - # The stream is valid only in the block, and has these methods: - # - # puts(str = ''):: outputs STR and CR LF. - # print(str):: outputs STR. - # printf(fmt, *args):: outputs sprintf(fmt,*args). - # write(str):: outputs STR and returns the length of written bytes. - # <<(str):: outputs STR and returns self. - # - # If a single CR ("\r") or LF ("\n") is found in the message, - # it is converted to the CR LF pair. You cannot send a binary - # message with this method. - # - # === Parameters - # - # +from_addr+ is a String or Net::SMTP::Address representing the source mail address. - # - # +to_addr+ is a String or Net::SMTP::Address or Array of them, representing - # the destination mail address or addresses. - # - # === Example - # - # Net::SMTP.start('smtp.example.com', 25) do |smtp| - # smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f| - # f.puts 'From: from@example.com' - # f.puts 'To: dest@example.com' - # f.puts 'Subject: test message' - # f.puts - # f.puts 'This is a test message.' - # end - # end - # - # === Errors - # - # This method may raise: - # - # * Net::SMTPServerBusy - # * Net::SMTPSyntaxError - # * Net::SMTPFatalError - # * Net::SMTPUnknownError - # * Net::ReadTimeout - # * IOError - # - def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream - to_addrs.flatten! - raise IOError, 'closed session' unless @socket - from_addr = Address.new(from_addr, 'SMTPUTF8') if any_require_smtputf8(to_addrs) && capable?('SMTPUTF8') - mailfrom from_addr - rcptto_list(to_addrs) {data(&block)} - end - - alias ready open_message_stream # obsolete - - # - # Authentication - # - - DEFAULT_AUTH_TYPE = :plain - - def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE) - check_auth_method authtype - check_auth_args user, secret - authenticator = Authenticator.auth_class(authtype).new(self) - authenticator.auth(user, secret) - end - - private - - def check_auth_method(type) - unless Authenticator.auth_class(type) - raise ArgumentError, "wrong authentication type #{type}" - end - end - - def auth_method(type) - "auth_#{type.to_s.downcase}".intern - end - - def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE) - unless user - raise ArgumentError, 'SMTP-AUTH requested but missing user name' - end - unless secret - raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase' - end - end - - # - # SMTP command dispatcher - # - - public - - # Aborts the current mail transaction - - def rset - getok('RSET') - end - - def starttls - getok('STARTTLS') - end - - def helo(domain) - getok("HELO #{domain}") - end - - def ehlo(domain) - getok("EHLO #{domain}") - end - - # +from_addr+ is +String+ or +Net::SMTP::Address+ - def mailfrom(from_addr) - addr = if requires_smtputf8(from_addr) && capable?("SMTPUTF8") - Address.new(from_addr, "SMTPUTF8") - else - Address.new(from_addr) - end - getok((["MAIL FROM:<#{addr.address}>"] + addr.parameters).join(' ')) - end - - def rcptto_list(to_addrs) - raise ArgumentError, 'mail destination not given' if to_addrs.empty? - to_addrs.flatten.each do |addr| - rcptto addr - end - yield - end - - # +to_addr+ is +String+ or +Net::SMTP::Address+ - def rcptto(to_addr) - addr = Address.new(to_addr) - getok((["RCPT TO:<#{addr.address}>"] + addr.parameters).join(' ')) - end - - # This method sends a message. - # If +msgstr+ is given, sends it as a message. - # If block is given, yield a message writer stream. - # You must write message before the block is closed. - # - # # Example 1 (by string) - # smtp.data(<] - attr_reader :parameters - - # :call-seq: - # initialize(address, parameter, ...) - # - # address +String+ or +Net::SMTP::Address+ - # parameter +String+ or +Hash+ - def initialize(address, *args, **kw_args) - if address.kind_of? Address - @address = address.address - @parameters = address.parameters - else - @address = address - @parameters = [] - end - @parameters = (parameters + args + [kw_args]).map{|param| Array(param)}.flatten(1).map{|param| Array(param).compact.join('=')}.uniq - end - - def to_s - @address - end - end - end # class SMTP - - SMTPSession = SMTP # :nodoc: -end - -require_relative 'smtp/authenticator' -Dir.glob("#{__dir__}/smtp/auth_*.rb") do |r| - require_relative r -end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/LICENSE.txt ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/LICENSE.txt --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/LICENSE.txt 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,22 @@ +Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/NEWS.md ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/NEWS.md --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/NEWS.md 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/NEWS.md 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,114 @@ +# NEWS + +## Version 0.4.0 (2023-09-20) + +### Improvements + +* add Net::SMTP::Authenticator class and auth_* methods are separated from the Net::SMTP class. + This allows you to add a new authentication method to Net::SMTP. + Create a class with an `auth` method that inherits Net::SMTP::Authenticator. + The `auth` method has two arguments, `user` and `secret`. + Send an instruction to the SMTP server by using the `continue` or `finish` method. + For more information, see lib/net/smtp/auto _*.rb. +* Add SMTPUTF8 support + +### Fixes + +* Revert "Replace Timeout.timeout with socket timeout" +* Fixed issue sending emails to unaffected recipients on 53x error + +### Others + +* Removed unnecessary Subversion keywords + +## Version 0.3.3 (2022-10-29) + +* No timeout library required +* Make the digest library optional + +## Version 0.3.2 (2022-09-28) + +* Make exception API compatible with what Ruby expects + +## Version 0.3.1 (2021-12-12) + +### Improvements + +* add Net::SMTP::Address. +* add Net::SMTP#capable? and Net::SMTP#capabilities. +* add Net::SMTP#tls_verify, Net::SMTP#tls_hostname, Net::SMTP#ssl_context_params + +## Version 0.3.0 (2021-10-14) + +### Improvements + +* Add `tls`, `starttls` keyword arguments. + ```ruby + # always use TLS connection for port 465. + Net::SMTP.start(hostname, 465, tls: true) + + # do not use starttls for localhost + Net::SMTP.start('localhost', starttls: false) + ``` + +### Incompatible changes + +* The tls_* paramter has been moved from start() to initialize(). + +## Version 0.2.2 (2021-10-09) + +* Add `response` to SMTPError exceptions. +* `Net::SMTP.start()` and `#start()` accepts `ssl_context_params` keyword argument. +* Replace `Timeout.timeout` with socket timeout. +* Remove needless files from gem. +* Add dependency on digest, timeout. + +## Version 0.2.1 (2020-11-18) + +### Fixes + +* Update the license for the default gems to dual licenses. +* Add dependency for net-protocol. + +## Version 0.2.0 (2020-11-15) + +### Incompatible changes + +* Verify the server's certificate by default. + If you don't want verification, specify `start(tls_verify: false)`. + + +* Use STARTTLS by default if possible. + If you don't want starttls, specify: + ``` + smtp = Net::SMTP.new(hostname, port) + smtp.disable_starttls + smtp.start do |s| + s.send_message .... + end + ``` + + +### Improvements + +* Net::SMTP.start and Net::SMTP#start arguments are keyword arguments. + ``` + start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... } + ``` + `password` is an alias of `secret`. + + +* Add `tls_hostname` parameter to `start()`. + If you want to use a different hostname than the certificate for the connection, you can specify the certificate hostname with `tls_hostname`. + + +* Add SNI support to net/smtp + +### Fixes + +* enable_starttls before disable_tls causes an error. +* TLS should not check the hostname when verify_mode is disabled. + +## Version 0.1.0 (2019-12-03) + +This is the first release of net-smtp gem. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/README.md ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/README.md --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/README.md 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/README.md 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,97 @@ +# Net::SMTP + +This library provides functionality to send internet mail via SMTP, the Simple Mail Transfer Protocol. + +For details of SMTP itself, see [RFC2821](http://www.ietf.org/rfc/rfc2821.txt). + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'net-smtp' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install net-smtp + +## Usage + +### Sending Messages + +You must open a connection to an SMTP server before sending messages. +The first argument is the address of your SMTP server, and the second +argument is the port number. Using SMTP.start with a block is the simplest +way to do this. This way, the SMTP connection is closed automatically +after the block is executed. + +```ruby +require 'net/smtp' +Net::SMTP.start('your.smtp.server', 25) do |smtp| + # Use the SMTP object smtp only in this block. +end +``` + +Replace 'your.smtp.server' with your SMTP server. Normally +your system manager or internet provider supplies a server +for you. + +Then you can send messages. + +```ruby +msgstr = < +To: Destination Address +Subject: test message +Date: Sat, 23 Jun 2001 16:26:43 +0900 +Message-Id: + +This is a test message. +END_OF_MESSAGE + +require 'net/smtp' +Net::SMTP.start('your.smtp.server', 25) do |smtp| + smtp.send_message msgstr, + 'your@mail.address', + 'his_address@example.com' +end +``` + +### Closing the Session + +You MUST close the SMTP session after sending messages, by calling +the #finish method: + +```ruby +# using SMTP#finish +smtp = Net::SMTP.start('your.smtp.server', 25) +smtp.send_message msgstr, 'from@address', 'to@address' +smtp.finish +``` + +You can also use the block form of SMTP.start/SMTP#start. This closes +the SMTP session automatically: + +```ruby +# using block form of SMTP.start +Net::SMTP.start('your.smtp.server', 25) do |smtp| + smtp.send_message msgstr, 'from@address', 'to@address' +end +``` + +I strongly recommend this scheme. This form is simpler and more robust. + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-smtp. diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_cram_md5.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_cram_md5.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_cram_md5.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_cram_md5.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,48 @@ +unless defined? OpenSSL + begin + require 'digest/md5' + rescue LoadError + end +end + +class Net::SMTP + class AuthCramMD5 < Net::SMTP::Authenticator + auth_type :cram_md5 + + def auth(user, secret) + challenge = continue('AUTH CRAM-MD5') + crammed = cram_md5_response(secret, challenge.unpack1('m')) + finish(base64_encode("#{user} #{crammed}")) + end + + IMASK = 0x36 + OMASK = 0x5c + + # CRAM-MD5: [RFC2195] + def cram_md5_response(secret, challenge) + tmp = digest_class::MD5.digest(cram_secret(secret, IMASK) + challenge) + digest_class::MD5.hexdigest(cram_secret(secret, OMASK) + tmp) + end + + CRAM_BUFSIZE = 64 + + def cram_secret(secret, mask) + secret = digest_class::MD5.digest(secret) if secret.size > CRAM_BUFSIZE + buf = secret.ljust(CRAM_BUFSIZE, "\0") + 0.upto(buf.size - 1) do |i| + buf[i] = (buf[i].ord ^ mask).chr + end + buf + end + + def digest_class + @digest_class ||= if defined?(OpenSSL::Digest) + OpenSSL::Digest + elsif defined?(::Digest) + ::Digest + else + raise '"openssl" or "digest" library is required' + end + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_login.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_login.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_login.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_login.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,11 @@ +class Net::SMTP + class AuthLogin < Net::SMTP::Authenticator + auth_type :login + + def auth(user, secret) + continue('AUTH LOGIN') + continue(base64_encode(user)) + finish(base64_encode(secret)) + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_plain.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_plain.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_plain.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/auth_plain.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,9 @@ +class Net::SMTP + class AuthPlain < Net::SMTP::Authenticator + auth_type :plain + + def auth(user, secret) + finish('AUTH PLAIN ' + base64_encode("\0#{user}\0#{secret}")) + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/authenticator.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/authenticator.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/authenticator.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp/authenticator.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,46 @@ +module Net + class SMTP + class Authenticator + def self.auth_classes + @classes ||= {} + end + + def self.auth_type(type) + Authenticator.auth_classes[type] = self + end + + def self.auth_class(type) + Authenticator.auth_classes[type.intern] + end + + attr_reader :smtp + + def initialize(smtp) + @smtp = smtp + end + + # @param arg [String] message to server + # @return [String] message from server + def continue(arg) + res = smtp.get_response arg + raise res.exception_class.new(res) unless res.continue? + res.string.split[1] + end + + # @param arg [String] message to server + # @return [Net::SMTP::Response] response from server + def finish(arg) + res = smtp.get_response arg + raise SMTPAuthenticationError.new(res) unless res.success? + res + end + + # @param str [String] + # @return [String] Base64 encoded string + def base64_encode(str) + # expects "str" may not become too long + [str].pack('m0') + end + end + end +end diff -Nru ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp.rb ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp.rb --- ruby3.3-3.3.0focal3/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/gems/net-smtp-0.4.0.1/lib/net/smtp.rb 2024-04-23 10:20:16.000000000 +0000 @@ -0,0 +1,1139 @@ +# frozen_string_literal: true + +# = net/smtp.rb +# +# Copyright (c) 1999-2007 Yukihiro Matsumoto. +# +# Copyright (c) 1999-2007 Minero Aoki. +# +# Written & maintained by Minero Aoki . +# +# Documented by William Webber and Minero Aoki. +# +# This program is free software. You can re-distribute and/or +# modify this program under the same terms as Ruby itself. +# +# See Net::SMTP for documentation. +# + +require 'net/protocol' +begin + require 'openssl' +rescue LoadError +end + +module Net + # Module mixed in to all SMTP error classes + module SMTPError + # This *class* is a module for backward compatibility. + # In later release, this module becomes a class. + + attr_reader :response + + def initialize(response, message: nil) + if response.is_a?(::Net::SMTP::Response) + @response = response + @message = message + else + @response = nil + @message = message || response + end + end + + def message + @message || response.message + end + end + + # Represents an SMTP authentication error. + class SMTPAuthenticationError < ProtoAuthError + include SMTPError + end + + # Represents SMTP error code 4xx, a temporary error. + class SMTPServerBusy < ProtoServerError + include SMTPError + end + + # Represents an SMTP command syntax error (error code 500) + class SMTPSyntaxError < ProtoSyntaxError + include SMTPError + end + + # Represents a fatal SMTP error (error code 5xx, except for 500) + class SMTPFatalError < ProtoFatalError + include SMTPError + end + + # Unexpected reply code returned from server. + class SMTPUnknownError < ProtoUnknownError + include SMTPError + end + + # Command is not supported on server. + class SMTPUnsupportedCommand < ProtocolError + include SMTPError + end + + # + # == What is This Library? + # + # This library provides functionality to send internet + # mail via SMTP, the Simple Mail Transfer Protocol. For details of + # SMTP itself, see [RFC5321] (http://www.ietf.org/rfc/rfc5321.txt). + # This library also implements SMTP authentication, which is often + # necessary for message composers to submit messages to their + # outgoing SMTP server, see + # [RFC6409](http://www.ietf.org/rfc/rfc6503.txt), + # and [SMTPUTF8](http://www.ietf.org/rfc/rfc6531.txt), which is + # necessary to send messages to/from addresses containing characters + # outside the ASCII range. + # + # == What is This Library NOT? + # + # This library does NOT provide functions to compose internet mails. + # You must create them by yourself. If you want better mail support, + # try RubyMail or TMail or search for alternatives in + # {RubyGems.org}[https://rubygems.org/] or {The Ruby + # Toolbox}[https://www.ruby-toolbox.com/]. + # + # FYI: the official specification on internet mail is: [RFC5322] (http://www.ietf.org/rfc/rfc5322.txt). + # + # == Examples + # + # === Sending Messages + # + # You must open a connection to an SMTP server before sending messages. + # The first argument is the address of your SMTP server, and the second + # argument is the port number. Using SMTP.start with a block is the simplest + # way to do this. This way, the SMTP connection is closed automatically + # after the block is executed. + # + # require 'net/smtp' + # Net::SMTP.start('your.smtp.server', 25) do |smtp| + # # Use the SMTP object smtp only in this block. + # end + # + # Replace 'your.smtp.server' with your SMTP server. Normally + # your system manager or internet provider supplies a server + # for you. + # + # Then you can send messages. + # + # msgstr = < + # To: Destination Address + # Subject: test message + # Date: Sat, 23 Jun 2001 16:26:43 +0900 + # Message-Id: + # + # This is a test message. + # END_OF_MESSAGE + # + # require 'net/smtp' + # Net::SMTP.start('your.smtp.server', 25) do |smtp| + # smtp.send_message msgstr, + # 'your@mail.address', + # 'his_address@example.com' + # end + # + # === Closing the Session + # + # You MUST close the SMTP session after sending messages, by calling + # the #finish method: + # + # # using SMTP#finish + # smtp = Net::SMTP.start('your.smtp.server', 25) + # smtp.send_message msgstr, 'from@address', 'to@address' + # smtp.finish + # + # You can also use the block form of SMTP.start/SMTP#start. This closes + # the SMTP session automatically: + # + # # using block form of SMTP.start + # Net::SMTP.start('your.smtp.server', 25) do |smtp| + # smtp.send_message msgstr, 'from@address', 'to@address' + # end + # + # I strongly recommend this scheme. This form is simpler and more robust. + # + # === HELO domain + # + # In almost all situations, you must provide a third argument + # to SMTP.start/SMTP#start. This is the domain name which you are on + # (the host to send mail from). It is called the "HELO domain". + # The SMTP server will judge whether it should send or reject + # the SMTP session by inspecting the HELO domain. + # + # Net::SMTP.start('your.smtp.server', 25 + # helo: 'mail.from.domain') { |smtp| ... } + # + # === SMTP Authentication + # + # The Net::SMTP class supports three authentication schemes; + # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554]) + # To use SMTP authentication, pass extra arguments to + # SMTP.start/SMTP#start. + # + # # PLAIN + # Net::SMTP.start('your.smtp.server', 25 + # user: 'Your Account', secret: 'Your Password', authtype: :plain) + # # LOGIN + # Net::SMTP.start('your.smtp.server', 25 + # user: 'Your Account', secret: 'Your Password', authtype: :login) + # + # # CRAM MD5 + # Net::SMTP.start('your.smtp.server', 25 + # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5) + # + class SMTP < Protocol + VERSION = "0.4.0.1" + + # The default SMTP port number, 25. + def SMTP.default_port + 25 + end + + # The default mail submission port number, 587. + def SMTP.default_submission_port + 587 + end + + # The default SMTPS port number, 465. + def SMTP.default_tls_port + 465 + end + + class << self + alias default_ssl_port default_tls_port + end + + def SMTP.default_ssl_context(ssl_context_params = nil) + context = OpenSSL::SSL::SSLContext.new + context.set_params(ssl_context_params || {}) + context + end + + # + # Creates a new Net::SMTP object. + # + # +address+ is the hostname or ip address of your SMTP + # server. +port+ is the port to connect to; it defaults to + # port 25. + # + # If +tls+ is true, enable TLS. The default is false. + # If +starttls+ is :always, enable STARTTLS, if +:auto+, use STARTTLS when the server supports it, + # if false, disable STARTTLS. + # + # If +tls_verify+ is true, verify the server's certificate. The default is true. + # If the hostname in the server certificate is different from +address+, + # it can be specified with +tls_hostname+. + # + # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to + # +OpenSSL::SSL::SSLContext#set_params+ + # + # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+. + # This method does not open the TCP connection. You can use + # SMTP.start instead of SMTP.new if you want to do everything + # at once. Otherwise, follow SMTP.new with SMTP#start. + # + def initialize(address, port = nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) + @address = address + @port = (port || SMTP.default_port) + @esmtp = true + @capabilities = nil + @socket = nil + @started = false + @open_timeout = 30 + @read_timeout = 60 + @error_occurred = false + @debug_output = nil + @tls = tls + @starttls = starttls + @ssl_context_tls = nil + @ssl_context_starttls = nil + @tls_verify = tls_verify + @tls_hostname = tls_hostname + @ssl_context_params = ssl_context_params + end + + # If +true+, verify th server's certificate. + attr_accessor :tls_verify + + # The hostname for verifying hostname in the server certificatate. + attr_accessor :tls_hostname + + # Hash for additional SSLContext parameters. + attr_accessor :ssl_context_params + + # Provide human-readable stringification of class state. + def inspect + "#<#{self.class} #{@address}:#{@port} started=#{@started}>" + end + + # + # Set whether to use ESMTP or not. This should be done before + # calling #start. Note that if #start is called in ESMTP mode, + # and the connection fails due to a ProtocolError, the SMTP + # object will automatically switch to plain SMTP mode and + # retry (but not vice versa). + # + attr_accessor :esmtp + + # +true+ if the SMTP object uses ESMTP (which it does by default). + alias esmtp? esmtp + + # true if server advertises STARTTLS. + # You cannot get valid value before opening SMTP session. + def capable_starttls? + capable?('STARTTLS') + end + + # true if the EHLO response contains +key+. + def capable?(key) + return nil unless @capabilities + @capabilities[key] ? true : false + end + + # The server capabilities by EHLO response + attr_reader :capabilities + + # true if server advertises AUTH PLAIN. + # You cannot get valid value before opening SMTP session. + def capable_plain_auth? + auth_capable?('PLAIN') + end + + # true if server advertises AUTH LOGIN. + # You cannot get valid value before opening SMTP session. + def capable_login_auth? + auth_capable?('LOGIN') + end + + # true if server advertises AUTH CRAM-MD5. + # You cannot get valid value before opening SMTP session. + def capable_cram_md5_auth? + auth_capable?('CRAM-MD5') + end + + def auth_capable?(type) + return nil unless @capabilities + return false unless @capabilities['AUTH'] + @capabilities['AUTH'].include?(type) + end + private :auth_capable? + + # Returns supported authentication methods on this server. + # You cannot get valid value before opening SMTP session. + def capable_auth_types + return [] unless @capabilities + return [] unless @capabilities['AUTH'] + @capabilities['AUTH'] + end + + # true if this object uses SMTP/TLS (SMTPS). + def tls? + @tls + end + + alias ssl? tls? + + # Enables SMTP/TLS (SMTPS: SMTP over direct TLS connection) for + # this object. Must be called before the connection is established + # to have any effect. +context+ is a OpenSSL::SSL::SSLContext object. + def enable_tls(context = nil) + raise 'openssl library not installed' unless defined?(OpenSSL::VERSION) + raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @starttls == :always + @tls = true + @ssl_context_tls = context + end + + alias enable_ssl enable_tls + + # Disables SMTP/TLS for this object. Must be called before the + # connection is established to have any effect. + def disable_tls + @tls = false + @ssl_context_tls = nil + end + + alias disable_ssl disable_tls + + # Returns truth value if this object uses STARTTLS. + # If this object always uses STARTTLS, returns :always. + # If this object uses STARTTLS when the server support TLS, returns :auto. + def starttls? + @starttls + end + + # true if this object uses STARTTLS. + def starttls_always? + @starttls == :always + end + + # true if this object uses STARTTLS when server advertises STARTTLS. + def starttls_auto? + @starttls == :auto + end + + # Enables SMTP/TLS (STARTTLS) for this object. + # +context+ is a OpenSSL::SSL::SSLContext object. + def enable_starttls(context = nil) + raise 'openssl library not installed' unless defined?(OpenSSL::VERSION) + raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls + @starttls = :always + @ssl_context_starttls = context + end + + # Enables SMTP/TLS (STARTTLS) for this object if server accepts. + # +context+ is a OpenSSL::SSL::SSLContext object. + def enable_starttls_auto(context = nil) + raise 'openssl library not installed' unless defined?(OpenSSL::VERSION) + raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @tls + @starttls = :auto + @ssl_context_starttls = context + end + + # Disables SMTP/TLS (STARTTLS) for this object. Must be called + # before the connection is established to have any effect. + def disable_starttls + @starttls = false + @ssl_context_starttls = nil + end + + # The address of the SMTP server to connect to. + attr_reader :address + + # The port number of the SMTP server to connect to. + attr_reader :port + + # Seconds to wait while attempting to open a connection. + # If the connection cannot be opened within this time, a + # Net::OpenTimeout is raised. The default value is 30 seconds. + attr_accessor :open_timeout + + # Seconds to wait while reading one block (by one read(2) call). + # If the read(2) call does not complete within this time, a + # Net::ReadTimeout is raised. The default value is 60 seconds. + attr_reader :read_timeout + + # Set the number of seconds to wait until timing-out a read(2) + # call. + def read_timeout=(sec) + @socket.read_timeout = sec if @socket + @read_timeout = sec + end + + # + # WARNING: This method causes serious security holes. + # Use this method for only debugging. + # + # Set an output stream for debug logging. + # You must call this before #start. + # + # # example + # smtp = Net::SMTP.new(addr, port) + # smtp.set_debug_output $stderr + # smtp.start do |smtp| + # .... + # end + # + def debug_output=(arg) + @debug_output = arg + end + + alias set_debug_output debug_output= + + # + # SMTP session control + # + + # + # :call-seq: + # start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } + # start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } + # + # Creates a new Net::SMTP object and connects to the server. + # + # This method is equivalent to: + # + # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname, ssl_context_params: nil) + # + # === Example + # + # Net::SMTP.start('your.smtp.server') do |smtp| + # smtp.send_message msgstr, 'from@example.com', ['dest@example.com'] + # end + # + # === Block Usage + # + # If called with a block, the newly-opened Net::SMTP object is yielded + # to the block, and automatically closed when the block finishes. If called + # without a block, the newly-opened Net::SMTP object is returned to + # the caller, and it is the caller's responsibility to close it when + # finished. + # + # === Parameters + # + # +address+ is the hostname or ip address of your smtp server. + # + # +port+ is the port to connect to; it defaults to port 25. + # + # +helo+ is the _HELO_ _domain_ provided by the client to the + # server (see overview comments); it defaults to 'localhost'. + # + # The remaining arguments are used for SMTP authentication, if required + # or desired. +user+ is the account name; +secret+ is your password + # or other authentication token; and +authtype+ is the authentication + # type, one of :plain, :login, or :cram_md5. See the discussion of + # SMTP Authentication in the overview notes. + # + # If +tls+ is true, enable TLS. The default is false. + # If +starttls+ is :always, enable STARTTLS, if +:auto+, use STARTTLS when the server supports it, + # if false, disable STARTTLS. + # + # If +tls_verify+ is true, verify the server's certificate. The default is true. + # If the hostname in the server certificate is different from +address+, + # it can be specified with +tls_hostname+. + # + # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to + # +OpenSSL::SSL::SSLContext#set_params+ + # + # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+. + # + # === Errors + # + # This method may raise: + # + # * Net::SMTPAuthenticationError + # * Net::SMTPServerBusy + # * Net::SMTPSyntaxError + # * Net::SMTPFatalError + # * Net::SMTPUnknownError + # * Net::OpenTimeout + # * Net::ReadTimeout + # * IOError + # + def SMTP.start(address, port = nil, *args, helo: nil, + user: nil, secret: nil, password: nil, authtype: nil, + tls: false, starttls: :auto, + tls_verify: true, tls_hostname: nil, ssl_context_params: nil, + &block) + raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 1..6)" if args.size > 4 + helo ||= args[0] || 'localhost' + user ||= args[1] + secret ||= password || args[2] + authtype ||= args[3] + new(address, port, tls: tls, starttls: starttls, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params).start(helo: helo, user: user, secret: secret, authtype: authtype, &block) + end + + # +true+ if the SMTP session has been started. + def started? + @started + end + + # + # :call-seq: + # start(helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... } + # start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } + # + # Opens a TCP connection and starts the SMTP session. + # + # === Parameters + # + # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see + # the discussion in the overview notes. + # + # If both of +user+ and +secret+ are given, SMTP authentication + # will be attempted using the AUTH command. +authtype+ specifies + # the type of authentication to attempt; it must be one of + # :login, :plain, and :cram_md5. See the notes on SMTP Authentication + # in the overview. + # + # === Block Usage + # + # When this methods is called with a block, the newly-started SMTP + # object is yielded to the block, and automatically closed after + # the block call finishes. Otherwise, it is the caller's + # responsibility to close the session when finished. + # + # === Example + # + # This is very similar to the class method SMTP.start. + # + # require 'net/smtp' + # smtp = Net::SMTP.new('smtp.mail.server', 25) + # smtp.start(helo: helo_domain, user: account, secret: password, authtype: authtype) do |smtp| + # smtp.send_message msgstr, 'from@example.com', ['dest@example.com'] + # end + # + # The primary use of this method (as opposed to SMTP.start) + # is probably to set debugging (#set_debug_output) or ESMTP + # (#esmtp=), which must be done before the session is + # started. + # + # === Errors + # + # If session has already been started, an IOError will be raised. + # + # This method may raise: + # + # * Net::SMTPAuthenticationError + # * Net::SMTPServerBusy + # * Net::SMTPSyntaxError + # * Net::SMTPFatalError + # * Net::SMTPUnknownError + # * Net::OpenTimeout + # * Net::ReadTimeout + # * IOError + # + def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil) + raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..4)" if args.size > 4 + helo ||= args[0] || 'localhost' + user ||= args[1] + secret ||= password || args[2] + authtype ||= args[3] + if defined?(OpenSSL::VERSION) + ssl_context_params = @ssl_context_params || {} + unless ssl_context_params.has_key?(:verify_mode) + ssl_context_params[:verify_mode] = @tls_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE + end + if @tls && @ssl_context_tls.nil? + @ssl_context_tls = SMTP.default_ssl_context(ssl_context_params) + end + if @starttls && @ssl_context_starttls.nil? + @ssl_context_starttls = SMTP.default_ssl_context(ssl_context_params) + end + end + if block_given? + begin + do_start helo, user, secret, authtype + return yield(self) + ensure + do_finish + end + else + do_start helo, user, secret, authtype + return self + end + end + + # Finishes the SMTP session and closes TCP connection. + # Raises IOError if not started. + def finish + raise IOError, 'not yet started' unless started? + do_finish + end + + private + + def tcp_socket(address, port) + TCPSocket.open address, port + end + + def do_start(helo_domain, user, secret, authtype) + raise IOError, 'SMTP session already started' if @started + if user or secret + check_auth_method(authtype || DEFAULT_AUTH_TYPE) + check_auth_args user, secret + end + s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do + tcp_socket(@address, @port) + end + logging "Connection opened: #{@address}:#{@port}" + @socket = new_internet_message_io(tls? ? tlsconnect(s, @ssl_context_tls) : s) + check_response critical { recv_response() } + do_helo helo_domain + if ! tls? and (starttls_always? or (capable_starttls? and starttls_auto?)) + unless capable_starttls? + raise SMTPUnsupportedCommand, "STARTTLS is not supported on this server" + end + starttls + @socket = new_internet_message_io(tlsconnect(s, @ssl_context_starttls)) + # helo response may be different after STARTTLS + do_helo helo_domain + end + authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user + @started = true + ensure + unless @started + # authentication failed, cancel connection. + s.close if s + @socket = nil + end + end + + def ssl_socket(socket, context) + OpenSSL::SSL::SSLSocket.new socket, context + end + + def tlsconnect(s, context) + verified = false + s = ssl_socket(s, context) + logging "TLS connection started" + s.sync_close = true + s.hostname = @tls_hostname || @address + ssl_socket_connect(s, @open_timeout) + verified = true + s + ensure + s.close unless verified + end + + def new_internet_message_io(s) + InternetMessageIO.new(s, read_timeout: @read_timeout, + debug_output: @debug_output) + end + + def do_helo(helo_domain) + res = @esmtp ? ehlo(helo_domain) : helo(helo_domain) + @capabilities = res.capabilities + rescue SMTPError + if @esmtp + @esmtp = false + @error_occurred = false + retry + end + raise + end + + def do_finish + quit if @socket and not @socket.closed? and not @error_occurred + ensure + @started = false + @error_occurred = false + @socket.close if @socket + @socket = nil + end + + def requires_smtputf8(address) + if address.kind_of? Address + !address.address.ascii_only? + else + !address.ascii_only? + end + end + + def any_require_smtputf8(addresses) + addresses.any?{ |a| requires_smtputf8(a) } + end + + # + # Message Sending + # + + public + + # + # Sends +msgstr+ as a message. Single CR ("\r") and LF ("\n") found + # in the +msgstr+, are converted into the CR LF pair. You cannot send a + # binary message with this method. +msgstr+ should include both + # the message headers and body. + # + # +from_addr+ is a String or Net::SMTP::Address representing the source mail address. + # + # +to_addr+ is a String or Net::SMTP::Address or Array of them, representing + # the destination mail address or addresses. + # + # === Example + # + # Net::SMTP.start('smtp.example.com') do |smtp| + # smtp.send_message msgstr, + # 'from@example.com', + # ['dest@example.com', 'dest2@example.com'] + # end + # + # Net::SMTP.start('smtp.example.com') do |smtp| + # smtp.send_message msgstr, + # Net::SMTP::Address.new('from@example.com', size: 12345), + # Net::SMTP::Address.new('dest@example.com', notify: :success) + # end + # + # === Errors + # + # This method may raise: + # + # * Net::SMTPServerBusy + # * Net::SMTPSyntaxError + # * Net::SMTPFatalError + # * Net::SMTPUnknownError + # * Net::ReadTimeout + # * IOError + # + def send_message(msgstr, from_addr, *to_addrs) + to_addrs.flatten! + raise IOError, 'closed session' unless @socket + from_addr = Address.new(from_addr, 'SMTPUTF8') if any_require_smtputf8(to_addrs) && capable?('SMTPUTF8') + mailfrom from_addr + rcptto_list(to_addrs) {data msgstr} + end + + alias send_mail send_message + alias sendmail send_message # obsolete + + # + # Opens a message writer stream and gives it to the block. + # The stream is valid only in the block, and has these methods: + # + # puts(str = ''):: outputs STR and CR LF. + # print(str):: outputs STR. + # printf(fmt, *args):: outputs sprintf(fmt,*args). + # write(str):: outputs STR and returns the length of written bytes. + # <<(str):: outputs STR and returns self. + # + # If a single CR ("\r") or LF ("\n") is found in the message, + # it is converted to the CR LF pair. You cannot send a binary + # message with this method. + # + # === Parameters + # + # +from_addr+ is a String or Net::SMTP::Address representing the source mail address. + # + # +to_addr+ is a String or Net::SMTP::Address or Array of them, representing + # the destination mail address or addresses. + # + # === Example + # + # Net::SMTP.start('smtp.example.com', 25) do |smtp| + # smtp.open_message_stream('from@example.com', ['dest@example.com']) do |f| + # f.puts 'From: from@example.com' + # f.puts 'To: dest@example.com' + # f.puts 'Subject: test message' + # f.puts + # f.puts 'This is a test message.' + # end + # end + # + # === Errors + # + # This method may raise: + # + # * Net::SMTPServerBusy + # * Net::SMTPSyntaxError + # * Net::SMTPFatalError + # * Net::SMTPUnknownError + # * Net::ReadTimeout + # * IOError + # + def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream + to_addrs.flatten! + raise IOError, 'closed session' unless @socket + from_addr = Address.new(from_addr, 'SMTPUTF8') if any_require_smtputf8(to_addrs) && capable?('SMTPUTF8') + mailfrom from_addr + rcptto_list(to_addrs) {data(&block)} + end + + alias ready open_message_stream # obsolete + + # + # Authentication + # + + DEFAULT_AUTH_TYPE = :plain + + def authenticate(user, secret, authtype = DEFAULT_AUTH_TYPE) + check_auth_method authtype + check_auth_args user, secret + authenticator = Authenticator.auth_class(authtype).new(self) + authenticator.auth(user, secret) + end + + private + + def check_auth_method(type) + unless Authenticator.auth_class(type) + raise ArgumentError, "wrong authentication type #{type}" + end + end + + def auth_method(type) + "auth_#{type.to_s.downcase}".intern + end + + def check_auth_args(user, secret, authtype = DEFAULT_AUTH_TYPE) + unless user + raise ArgumentError, 'SMTP-AUTH requested but missing user name' + end + unless secret + raise ArgumentError, 'SMTP-AUTH requested but missing secret phrase' + end + end + + # + # SMTP command dispatcher + # + + public + + # Aborts the current mail transaction + + def rset + getok('RSET') + end + + def starttls + getok('STARTTLS') + end + + def helo(domain) + getok("HELO #{domain}") + end + + def ehlo(domain) + getok("EHLO #{domain}") + end + + # +from_addr+ is +String+ or +Net::SMTP::Address+ + def mailfrom(from_addr) + addr = if requires_smtputf8(from_addr) && capable?("SMTPUTF8") + Address.new(from_addr, "SMTPUTF8") + else + Address.new(from_addr) + end + getok((["MAIL FROM:<#{addr.address}>"] + addr.parameters).join(' ')) + end + + def rcptto_list(to_addrs) + raise ArgumentError, 'mail destination not given' if to_addrs.empty? + to_addrs.flatten.each do |addr| + rcptto addr + end + yield + end + + # +to_addr+ is +String+ or +Net::SMTP::Address+ + def rcptto(to_addr) + addr = Address.new(to_addr) + getok((["RCPT TO:<#{addr.address}>"] + addr.parameters).join(' ')) + end + + # This method sends a message. + # If +msgstr+ is given, sends it as a message. + # If block is given, yield a message writer stream. + # You must write message before the block is closed. + # + # # Example 1 (by string) + # smtp.data(<] + attr_reader :parameters + + # :call-seq: + # initialize(address, parameter, ...) + # + # address +String+ or +Net::SMTP::Address+ + # parameter +String+ or +Hash+ + def initialize(address, *args, **kw_args) + if address.kind_of? Address + @address = address.address + @parameters = address.parameters + else + @address = address + @parameters = [] + end + @parameters = (parameters + args + [kw_args]).map{|param| Array(param)}.flatten(1).map{|param| Array(param).compact.join('=')}.uniq + end + + def to_s + @address + end + end + end # class SMTP + + SMTPSession = SMTP # :nodoc: +end + +require_relative 'smtp/authenticator' +Dir.glob("#{__dir__}/smtp/auth_*.rb") do |r| + require_relative r +end diff -Nru ruby3.3-3.3.0focal3/.bundle/specifications/net-ftp-0.3.3.gemspec ruby3.3-3.3.1focal4/.bundle/specifications/net-ftp-0.3.3.gemspec --- ruby3.3-3.3.0focal3/.bundle/specifications/net-ftp-0.3.3.gemspec 2023-12-25 05:59:40.000000000 +0000 +++ ruby3.3-3.3.1focal4/.bundle/specifications/net-ftp-0.3.3.gemspec 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -# -*- encoding: utf-8 -*- -# stub: net-ftp 0.3.3 ruby lib - -Gem::Specification.new do |s| - s.name = "net-ftp".freeze - s.version = "0.3.3" - - s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= - s.metadata = { "homepage_uri" => "https://github.com/ruby/net-ftp", "source_code_uri" => "https://github.com/ruby/net-ftp" } if s.respond_to? :metadata= - s.require_paths = ["lib".freeze] - s.authors = ["Shugo Maeda".freeze] - s.date = "2023-12-15" - s.description = "Support for the File Transfer Protocol.".freeze - s.email = ["shugo@ruby-lang.org".freeze] - s.files = [".github/dependabot.yml".freeze, ".github/workflows/test.yml".freeze, ".gitignore".freeze, "Gemfile".freeze, "LICENSE.txt".freeze, "README.md".freeze, "Rakefile".freeze, "lib/net/ftp.rb".freeze, "net-ftp.gemspec".freeze] - s.homepage = "https://github.com/ruby/net-ftp".freeze - s.licenses = ["Ruby".freeze, "BSD-2-Clause".freeze] - s.required_ruby_version = Gem::Requirement.new(">= 2.6.0".freeze) - s.rubygems_version = "3.3.5".freeze - s.summary = "Support for the File Transfer Protocol.".freeze - - if s.respond_to? :specification_version then - s.specification_version = 4 - end - - if s.respond_to? :add_runtime_dependency then - s.add_runtime_dependency(%q.freeze, [">= 0"]) - s.add_runtime_dependency(%q