class Mongo::URI

The URI class provides a way for users to parse the MongoDB uri as defined in the connection string format spec.

docs.mongodb.org/manual/reference/connection-string/

@example Use the uri string to make a client connection.

uri = Mongo::URI.new('mongodb://localhost:27017')
client = Mongo::Client.new(uri.servers, uri.options)
client.login(uri.credentials)
client[uri.database]

@since 2.0.0

Constants

AUTH_DELIM

The character delimiting auth credentials.

@since 2.1.0

AUTH_MECH_MAP

Map of URI authentication mechanisms to Ruby driver mechanisms

@since 2.0.0

AUTH_USER_PWD_DELIM

The character separating a username from the password.

@since 2.1.0

DATABASE_DELIM

The character delimiting a database.

@since 2.1.0

FORMAT

MongoDB URI format specification.

@since 2.0.0

HELP

MongoDB URI (connection string) documentation url

@since 2.0.0

HOST_DELIM

The character delimiting hosts.

@since 2.1.0

HOST_PORT_DELIM

The character separating a host and port.

@since 2.1.0

INDIV_URI_OPTS_DELIM

The character delimiting multiple options.

@since 2.1.0 @deprecated

INVALID_HOST

Error details for a missing host.

@since 2.1.0

INVALID_OPTS_DELIM

Error details for providing options without a database delimiter.

@since 2.1.0

INVALID_OPTS_VALUE_DELIM

Error details for an invalid options format.

@since 2.1.0

INVALID_PORT

Error details for an invalid port.

@since 2.1.0

INVALID_SCHEME

Error details for an invalid scheme.

@since 2.1.0 @deprecated

MONGODB_SCHEME

The mongodb connection string scheme root.

@since 2.5.0

MONGODB_SRV_SCHEME

The mongodb srv protocol connection string scheme root.

@since 2.5.0

PERCENT_CHAR

Percent sign that must be encoded in user creds.

@since 2.5.1

READ_MODE_MAP

Map of URI read preference modes to Ruby driver read preference modes

@since 2.0.0

REPEATABLE_OPTIONS

Options that are allowed to appear more than once in the uri.

In order to follow the URI options spec requirement that all instances of 'tls' and 'ssl' have the same value, we need to keep track of all of the values passed in for those options. Assuming they don't conflict, they will be condensed to a single value immediately after parsing the URI.

@since 2.1.0

SCHEME

The mongodb connection string scheme.

@deprecated Will be removed in 3.0.

@since 2.0.0

SCHEME_DELIM

Scheme delimiter.

@since 2.5.0

UNESCAPED_DATABASE

Error details for a non-urlencoded auth database name.

@since 2.1.0

UNESCAPED_UNIX_SOCKET

Error details for a non-urlencoded unix socket path.

@since 2.1.0

UNESCAPED_USER_PWD

Error details for an non-urlencoded user name or password.

@since 2.1.0

UNIX_SOCKET

Unix socket suffix.

@since 2.1.0

UNSAFE

Unsafe characters that must be urlencoded.

@since 2.1.0

URI_OPTS_DELIM

The character delimiting options.

@since 2.1.0

URI_OPTS_VALUE_DELIM

The character delimiting an option and its value.

@since 2.1.0

Attributes

options[R]

The uri parser object options.

@since 2.0.0

servers[R]

The servers specified in the uri.

@since 2.0.0

uri_options[R]

Mongo::Options::Redacted of the options specified in the uri.

@since 2.1.0

Public Class Methods

get(string, opts = {}) click to toggle source

Get either a URI object or a SRVProtocol URI object.

@example Get the uri object.

URI.get(string)

@param [ String ] string The URI to parse. @param [ Hash ] opts The options.

@option options [ Logger ] :logger A custom logger to use.

@return [URI, URI::SRVProtocol] The uri object.

@since 2.5.0

# File lib/mongo/uri.rb, line 230
def self.get(string, opts = {})
  unless string
    raise Error::InvalidURI.new(string, 'URI must be a string, not nil.')
  end
  if string.empty?
    raise Error::InvalidURI.new(string, 'Cannot parse an empty URI.')
  end

  scheme, _, remaining = string.partition(SCHEME_DELIM)
  case scheme
    when MONGODB_SCHEME
      URI.new(string, opts)
    when MONGODB_SRV_SCHEME
      SRVProtocol.new(string, opts)
    else
      raise Error::InvalidURI.new(string, "Invalid scheme '#{scheme}'. Scheme must be '#{MONGODB_SCHEME}' or '#{MONGODB_SRV_SCHEME}'")
  end
end
new(string, options = {}) click to toggle source

Create the new uri from the provided string.

@example Create the new URI.

URI.new('mongodb://localhost:27017')

@param [ String ] string The URI to parse. @param [ Hash ] options The options.

@option options [ Logger ] :logger A custom logger to use.

@raise [ Error::InvalidURI ] If the uri does not match the spec.

@since 2.0.0

# File lib/mongo/uri.rb, line 284
def initialize(string, options = {})
  unless string
    raise Error::InvalidURI.new(string, 'URI must be a string, not nil.')
  end
  if string.empty?
    raise Error::InvalidURI.new(string, 'Cannot parse an empty URI.')
  end

  @string = string
  @options = options
  parsed_scheme, _, remaining = string.partition(SCHEME_DELIM)
  unless parsed_scheme == scheme
    raise_invalid_error!("Invalid scheme '#{parsed_scheme}'. Scheme must be '#{MONGODB_SCHEME}'. Use URI#get to parse SRV URIs.")
  end
  if remaining.empty?
    raise_invalid_error!('No hosts in the URI')
  end
  parse!(remaining)
  validate_uri_options!
end

Public Instance Methods

client_options() click to toggle source

Gets the options hash that needs to be passed to a Mongo::Client on instantiation, so we don't have to merge the credentials and database in at that point - we only have a single point here.

@example Get the client options.

uri.client_options

@return [ Mongo::Options::Redacted ] The options passed to the Mongo::Client

@since 2.0.0

# File lib/mongo/uri.rb, line 259
def client_options
  opts = uri_options.tap do |opts|
    opts[:database] = @database if @database
  end

  @user ? opts.merge(credentials) : opts
end
credentials() click to toggle source

Get the credentials provided in the URI.

@example Get the credentials.

uri.credentials

@return [ Hash ] The credentials.

* :user [ String ] The user.
* :password [ String ] The provided password.

@since 2.0.0

# File lib/mongo/uri.rb, line 315
def credentials
  { :user => @user, :password => @password }
end
database() click to toggle source

Get the database provided in the URI.

@example Get the database.

uri.database

@return [String] The database.

@since 2.0.0

# File lib/mongo/uri.rb, line 327
def database
  @database ? @database : Database::ADMIN
end
srv_records() click to toggle source
# File lib/mongo/uri.rb, line 267
def srv_records
  nil
end

Private Instance Methods

decode(value) click to toggle source
# File lib/mongo/uri.rb, line 457
def decode(value)
  ::URI::DEFAULT_PARSER.unescape(value)
end
encode(value) click to toggle source
# File lib/mongo/uri.rb, line 461
def encode(value)
  CGI.escape(value).gsub('+', '%20')
end
extract_db_opts!(string) click to toggle source
# File lib/mongo/uri.rb, line 385
def extract_db_opts!(string)
  db_opts, _, creds_hosts = string.reverse.partition(DATABASE_DELIM)
  db_opts, creds_hosts = creds_hosts, db_opts if creds_hosts.empty?
  if db_opts.empty? && creds_hosts.include?(URI_OPTS_DELIM)
    raise_invalid_error!(INVALID_OPTS_DELIM)
  end
  [ creds_hosts, db_opts ].map { |s| s.reverse }
end
options_mapper() click to toggle source
# File lib/mongo/uri.rb, line 394
def options_mapper
  @options_mapper ||= OptionsMapper.new(
    logger: @options[:logger],
  )
end
parse!(remaining) click to toggle source
# File lib/mongo/uri.rb, line 337
def parse!(remaining)
  hosts_and_db, options = remaining.split('?', 2)
  if options && options.index('?')
    raise_invalid_error!("Options contain an unescaped question mark (?), or the database name contains a question mark and was not escaped")
  end

  if options && !hosts_and_db.index('/')
    raise_invalid_error!("MongoDB URI must have a slash (/) after the hosts if options are given")
  end

  hosts, db = hosts_and_db.split('/', 2)
  if db && db.index('/')
    raise_invalid_error!("Database name contains an unescaped slash (/): #{db}")
  end

  if hosts.index('@')
    creds, hosts = hosts.split('@', 2)
    if hosts.empty?
      raise_invalid_error!("Empty hosts list")
    end
    if hosts.index('@')
      raise_invalid_error!("Unescaped @ in auth info")
    end
  end

  unless hosts.length > 0
    raise_invalid_error!("Missing host; at least one must be provided")
  end

  @servers = hosts.split(',').map do |host|
    if host.empty?
      raise_invalid_error!('Empty host given in the host list')
    end
    decode(host).tap do |host|
      validate_address_str!(host)
    end
  end

  @user = parse_user!(creds)
  @password = parse_password!(creds)
  @uri_options = Options::Redacted.new(parse_uri_options!(options))
  if db
    @database = parse_database!(db)
  end
rescue Error::InvalidAddress => e
  raise_invalid_error!(e.message)
end
parse_database!(string) click to toggle source
# File lib/mongo/uri.rb, line 444
def parse_database!(string)
  raise_invalid_error!(UNESCAPED_DATABASE) if string =~ UNSAFE
  decode(string) if string.length > 0
end
parse_password!(string) click to toggle source
# File lib/mongo/uri.rb, line 431
def parse_password!(string)
  if (string && pwd = string.partition(AUTH_USER_PWD_DELIM)[2])
    if pwd.length > 0
      raise_invalid_error!(UNESCAPED_USER_PWD) if pwd =~ UNSAFE
      pwd_decoded = decode(pwd)
      if pwd_decoded =~ PERCENT_CHAR && encode(pwd_decoded) != pwd
        raise_invalid_error!(UNESCAPED_USER_PWD)
      end
      pwd_decoded
    end
  end
end
parse_uri_options!(string) click to toggle source
# File lib/mongo/uri.rb, line 400
def parse_uri_options!(string)
  uri_options = {}
  unless string
    return uri_options
  end
  string.split('&').each do |option_str|
    if option_str.empty?
      next
    end
    key, value = option_str.split('=', 2)
    if value.nil?
      raise_invalid_error!("Option #{key} has no value")
    end
    key = decode(key)
    value = decode(value)
    options_mapper.add_uri_option(key, value, uri_options)
  end
  uri_options
end
parse_user!(string) click to toggle source
# File lib/mongo/uri.rb, line 420
def parse_user!(string)
  if (string && user = string.partition(AUTH_USER_PWD_DELIM)[0])
    raise_invalid_error!(UNESCAPED_USER_PWD) if user =~ UNSAFE
    user_decoded = decode(user)
    if user_decoded =~ PERCENT_CHAR && encode(user_decoded) != user
      raise_invalid_error!(UNESCAPED_USER_PWD)
    end
    user_decoded
  end
end
raise_invalid_error!(details) click to toggle source
# File lib/mongo/uri.rb, line 449
def raise_invalid_error!(details)
  raise Error::InvalidURI.new(@string, details, FORMAT)
end
raise_invalid_error_no_fmt!(details) click to toggle source
# File lib/mongo/uri.rb, line 453
def raise_invalid_error_no_fmt!(details)
  raise Error::InvalidURI.new(@string, details)
end
scheme() click to toggle source
# File lib/mongo/uri.rb, line 333
def scheme
  MONGODB_SCHEME
end
validate_uri_options!() click to toggle source
# File lib/mongo/uri.rb, line 465
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?
    unless uri_options[:ssl].uniq.length == 1
      raise_invalid_error_no_fmt!("all instances of 'tls' and 'ssl' must have the same value")
    end

    uri_options[:ssl] = uri_options[:ssl].first
  end

  # Check for conflicting TLS insecure options.
  unless uri_options[:ssl_verify].nil?
    unless uri_options[:ssl_verify_certificate].nil?
      raise_invalid_error_no_fmt!("'tlsInsecure' and 'tlsAllowInvalidCertificates' cannot both be specified")
    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

  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?
    begin
      WriteConcern.get(uri_options[:write_concern])
    rescue Error::InvalidWriteConcern => e
      raise_invalid_error_no_fmt!("#{e.class}: #{e}")
    end
  end

  if uri_options[:direct_connection]
    if uri_options[:connect] && uri_options[:connect].to_s != 'direct'
      raise_invalid_error_no_fmt!("directConnection=true cannot be used with connect=#{uri_options[:connect]}")
    end
    if servers.length > 1
      raise_invalid_error_no_fmt!("directConnection=true cannot be used with multiple seeds")
    end
  elsif uri_options[:direct_connection] == false && uri_options[:connect].to_s == 'direct'
    raise_invalid_error_no_fmt!("directConnection=false cannot be used with connect=direct")
  end

  if uri_options[:load_balanced]
    if servers.length > 1
      raise_invalid_error_no_fmt!("loadBalanced=true cannot be used with multiple seeds")
    end

    if uri_options[:direct_connection]
      raise_invalid_error_no_fmt!("directConnection=true cannot be used with loadBalanced=true")
    end

    if uri_options[:connect] && uri_options[:connect].to_sym == :direct
      raise_invalid_error_no_fmt!("connect=direct cannot be used with loadBalanced=true")
    end

    if uri_options[:replica_set]
      raise_invalid_error_no_fmt!("loadBalanced=true cannot be used with replicaSet option")
    end
  end
end