lib/mongo/uri.rb in mongo-2.13.3 vs lib/mongo/uri.rb in mongo-2.14.0.rc1

- old
+ new

@@ -386,10 +386,16 @@ raise_invalid_error!(INVALID_OPTS_DELIM) end [ creds_hosts, db_opts ].map { |s| s.reverse } end + def options_mapper + @options_mapper ||= OptionsMapper.new( + logger: @options[:logger], + ) + end + def parse_uri_options!(string) uri_options = {} unless string return uri_options end @@ -401,11 +407,11 @@ if value.nil? raise_invalid_error!("Option #{key} has no value") end key = decode(key) value = decode(value) - add_uri_option(key, value, uri_options) + options_mapper.add_uri_option(key, value, uri_options) end uri_options end def parse_user!(string) @@ -451,397 +457,10 @@ def encode(value) CGI.escape(value).gsub('+', '%20') end - # Hash for storing map of URI option parameters to conversion strategies - URI_OPTION_MAP = {} - - # Simple internal dsl to register a MongoDB URI option in the URI_OPTION_MAP. - # - # @param uri_key [String] The MongoDB URI option to register. - # @param name [Symbol] The name of the option in the driver. - # @param extra [Hash] Extra options. - # * :group [Symbol] Nested hash where option will go. - # * :type [Symbol] Name of function to transform value. - def self.uri_option(uri_key, name, extra = {}) - URI_OPTION_MAP[uri_key] = { :name => name }.merge(extra) - end - - # Replica Set Options - uri_option 'replicaset', :replica_set - - # Timeout Options - uri_option 'connecttimeoutms', :connect_timeout, :type => :ms - uri_option 'sockettimeoutms', :socket_timeout, :type => :ms - uri_option 'serverselectiontimeoutms', :server_selection_timeout, :type => :ms - uri_option 'localthresholdms', :local_threshold, :type => :ms - uri_option 'heartbeatfrequencyms', :heartbeat_frequency, :type => :ms - uri_option 'maxidletimems', :max_idle_time, :type => :ms - - # Write Options - uri_option 'w', :w, :group => :write_concern, type: :w - uri_option 'journal', :j, :group => :write_concern, :type => :bool - uri_option 'fsync', :fsync, :group => :write_concern, type: :bool - uri_option 'wtimeoutms', :wtimeout, :group => :write_concern, :type => :integer - - # Read Options - uri_option 'readpreference', :mode, :group => :read, :type => :read_mode - uri_option 'readpreferencetags', :tag_sets, :group => :read, :type => :read_tags - uri_option 'maxstalenessseconds', :max_staleness, :group => :read, :type => :max_staleness - - # Pool options - uri_option 'minpoolsize', :min_pool_size, :type => :integer - uri_option 'maxpoolsize', :max_pool_size, :type => :integer - uri_option 'waitqueuetimeoutms', :wait_queue_timeout, :type => :ms - - # Security Options - uri_option 'ssl', :ssl, :type => :repeated_bool - uri_option 'tls', :ssl, :type => :repeated_bool - uri_option 'tlsallowinvalidcertificates', :ssl_verify_certificate, - :type => :inverse_bool - uri_option 'tlsallowinvalidhostnames', :ssl_verify_hostname, - :type => :inverse_bool - uri_option 'tlscafile', :ssl_ca_cert - uri_option 'tlscertificatekeyfile', :ssl_cert - uri_option 'tlscertificatekeyfilepassword', :ssl_key_pass_phrase - uri_option 'tlsinsecure', :ssl_verify, :type => :inverse_bool - - # Topology options - uri_option 'directconnection', :direct_connection, type: :bool - uri_option 'connect', :connect, type: :symbol - - # Auth Options - uri_option 'authsource', :auth_source - uri_option 'authmechanism', :auth_mech, :type => :auth_mech - uri_option 'authmechanismproperties', :auth_mech_properties, :type => :auth_mech_props - - # Client Options - uri_option 'appname', :app_name - uri_option 'compressors', :compressors, :type => :array - uri_option 'readconcernlevel', :level, group: :read_concern, type: :symbol - uri_option 'retryreads', :retry_reads, :type => :bool - uri_option 'retrywrites', :retry_writes, :type => :bool - uri_option 'zlibcompressionlevel', :zlib_compression_level, :type => :zlib_compression_level - - # Applies URI value transformation by either using the default cast - # or a transformation appropriate for the given type. - # - # @param key [String] URI option name. - # @param value [String] The value to be transformed. - # @param type [Symbol] The transform method. - def apply_transform(key, value, type) - if type - if respond_to?("convert_#{type}", true) - send("convert_#{type}", key, value) - else - send(type, value) - end - else - value - end - end - - # Selects the output destination for an option. - # - # @param [Hash] uri_options The base target. - # @param [Symbol] group Group subtarget. - # - # @return [Hash] The target for the option. - def select_target(uri_options, group = nil) - if group - uri_options[group] ||= {} - else - uri_options - end - end - - # Merges a new option into the target. - # - # If the option exists at the target destination the merge will - # be an addition. - # - # Specifically required to append an additional tag set - # to the array of tag sets without overwriting the original. - # - # @param target [Hash] The destination. - # @param value [Object] The value to be merged. - # @param name [Symbol] The name of the option. - def merge_uri_option(target, value, name) - if target.key?(name) - if REPEATABLE_OPTIONS.include?(name) - target[name] += value - else - log_warn("Repeated option key: #{name}.") - end - else - target.merge!(name => value) - end - end - - # Adds an option to the uri options hash via the supplied strategy. - # - # Acquires a target for the option based on group. - # Transforms the value. - # Merges the option into the target. - # - # @param key [String] URI option name. - # @param value [String] The value of the option. - # @param uri_options [Hash] The base option target. - def add_uri_option(key, value, uri_options) - strategy = URI_OPTION_MAP[key.downcase] - if strategy.nil? - log_warn("Unsupported URI option '#{key}' on URI '#{@string}'. It will be ignored.") - return - end - - target = select_target(uri_options, strategy[:group]) - value = apply_transform(key, value, strategy[:type]) - merge_uri_option(target, value, strategy[:name]) - end - - # Authentication mechanism transformation. - # - # @param value [String] The authentication mechanism. - # - # @return [Symbol] The transformed authentication mechanism. - def auth_mech(value) - (AUTH_MECH_MAP[value.upcase] || value).tap do |mech| - log_warn("#{value} is not a valid auth mechanism") unless mech - end - end - - # Read preference mode transformation. - # - # @param value [String] The read mode string value. - # - # @return [Symbol] The read mode symbol. - def read_mode(value) - READ_MODE_MAP[value.downcase] || value - end - - # Read preference tags transformation. - # - # @param value [String] The string representing tag set. - # - # @return [Array<Hash>] Array with tag set. - def read_tags(value) - [read_set(value)] - end - - # Read preference tag set extractor. - # - # @param value [String] The tag set string. - # - # @return [Hash] The tag set hash. - def read_set(value) - hash_extractor('readPreferenceTags', value) - end - - # Auth mechanism properties extractor. - # - # @param value [ String ] The auth mechanism properties string. - # - # @return [ Hash ] The auth mechanism properties hash. - def auth_mech_props(value) - properties = hash_extractor('authMechanismProperties', value) - if properties && properties[:canonicalize_host_name] - properties.merge!(canonicalize_host_name: - properties[:canonicalize_host_name].downcase == 'true') - end - properties - end - - # Parses the zlib compression level. - # - # @param value [ String ] The zlib compression level string. - # - # @return [ Integer | nil ] The compression level value if it is between -1 and 9 (inclusive), - # otherwise nil (and a warning will be logged). - def zlib_compression_level(value) - if /\A-?\d+\z/ =~ value - i = value.to_i - - if i >= -1 && i <= 9 - return i - end - end - - log_warn("#{value} is not a valid zlibCompressionLevel") - nil - end - - # Converts the value into a boolean and returns it wrapped in an array. - # - # @param name [ String ] Name of the URI option being processed. - # @param value [ String ] URI option value. - # - # @return [ Array<true | false> ] The boolean value parsed and wraped - # in an array. - def convert_repeated_bool(name, value) - [convert_bool(name, value)] - end - - # Converts +value+ into an integer. - # - # If the value is not a valid integer, warns and returns nil. - # - # @param name [ String ] Name of the URI option being processed. - # @param value [ String ] URI option value. - # - # @return [ nil | Integer ] Converted value. - def convert_integer(name, value) - unless /\A\d+\z/ =~ value - log_warn("#{value} is not a valid integer for #{name}") - return nil - end - - value.to_i - end - - # Converts +value+ into a symbol. - # - # @param name [ String ] Name of the URI option being processed. - # @param value [ String ] URI option value. - # - # @return [ Symbol ] Converted value. - def convert_symbol(name, value) - value.to_sym - end - - # Converts +value+ as a write concern. - # - # If +value+ is the word "majority", returns the symbol :majority. - # If +value+ is a number, returns the number as an integer. - # Otherwise returns the string +value+ unchanged. - # - # @param name [ String ] Name of the URI option being processed. - # @param value [ String ] URI option value. - # - # @return [ Integer | Symbol | String ] Converted value. - def convert_w(name, value) - case value - when 'majority' - :majority - when /\A[0-9]+\z/ - value.to_i - else - value - end - end - - # Converts +value+ to a boolean. - # - # Returns true for 'true', false for 'false', otherwise nil. - # - # @param name [ String ] Name of the URI option being processed. - # @param value [ String ] URI option value. - # - # @return [ true | false | nil ] Converted value. - def convert_bool(name, value) - case value - when "true", 'TRUE' - true - when "false", 'FALSE' - false - else - log_warn("invalid boolean option for #{name}: #{value}") - nil - end - end - - # Parses a boolean value and returns its inverse. - # - # @param value [ String ] The URI option value. - # - # @return [ true | false | nil ] The inverse of the boolean value parsed out, otherwise nil - # (and a warning will be logged). - def convert_inverse_bool(name, value) - b = convert_bool(name, value) - - if b.nil? - nil - else - !b - end - end - - # Parses the max staleness value, which must be either "0" or an integer greater or equal to 90. - # - # @param value [ String ] The max staleness string. - # - # @return [ Integer | nil ] The max staleness integer parsed out if it is valid, otherwise nil - # (and a warning will be logged). - def max_staleness(value) - if /\A-?\d+\z/ =~ value - int = value.to_i - - if int == -1 - int = nil - end - - if int && (int >= 0 && int < 90 || int < 0) - log_warn("max staleness should be either 0 or greater than 90: #{value}") - end - - return int - end - - log_warn("Invalid max staleness value: #{value}") - nil - end - - # Ruby's convention is to provide timeouts in seconds, not milliseconds and - # to use fractions where more precision is necessary. The connection string - # options are always in MS so we provide an easy conversion type. - # - # @param [ Integer ] value The millisecond value. - # - # @return [ Float ] The seconds value. - # - # @since 2.0.0 - def convert_ms(name, value) - unless /\A-?\d+(\.\d+)?\z/ =~ value - log_warn("Invalid ms value for #{name}: #{value}") - return nil - end - - if value[0] == '-' - log_warn("#{name} cannot be a negative number") - return nil - end - - value.to_f / 1000 - end - - # Extract values from the string and put them into a nested hash. - # - # @param value [ String ] The string to build a hash from. - # - # @return [ Hash ] The hash built from the string. - def hash_extractor(name, value) - h = {} - value.split(',').each do |tag| - k, v = tag.split(':') - if v.nil? - log_warn("Invalid hash value for #{name}: key `#{k}` does not have a value: #{value}") - end - - h[k.downcase.to_sym] = v - end - h - end - - # Extract values from the string and put them into an array. - # - # @param [ String ] value The string to build an array from. - # - # @return [ Array ] The array built from the string. - def array(value) - value.split(',') - end - def validate_uri_options! # The URI options spec requires that we raise an error if there are conflicting values of # 'tls' and 'ssl'. In order to fulfill this, we parse the values of each instance into an # array; assuming all values in the array are the same, we replace the array with that value. unless uri_options[:ssl].nil? || uri_options[:ssl].empty? @@ -859,14 +478,25 @@ end unless uri_options[:ssl_verify_hostname].nil? raise_invalid_error_no_fmt!("tlsInsecure' and 'tlsAllowInvalidHostnames' cannot both be specified") end + + unless uri_options[:ssl_verify_ocsp_endpoint].nil? + raise_invalid_error_no_fmt!("tlsInsecure' and 'tlsDisableOCSPEndpointCheck' cannot both be specified") + end end - # Since we know that the only URI option that sets :ssl_cert is "tlsCertificateKeyFile", any - # value set for :ssl_cert must also be set for :ssl_key. + unless uri_options[:ssl_verify_certificate].nil? + unless uri_options[:ssl_verify_ocsp_endpoint].nil? + raise_invalid_error_no_fmt!("tlsAllowInvalidCertificates' and 'tlsDisableOCSPEndpointCheck' cannot both be specified") + end + end + + # Since we know that the only URI option that sets :ssl_cert is + # "tlsCertificateKeyFile", any value set for :ssl_cert must also be set + # for :ssl_key. if uri_options[:ssl_cert] uri_options[:ssl_key] = uri_options[:ssl_cert] end if uri_options[:write_concern] && !uri_options[:write_concern].empty? @@ -889,6 +519,7 @@ end end end end +require 'mongo/uri/options_mapper' require 'mongo/uri/srv_protocol'