mongo 2.10.5 → 2.11.0.rc0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/lib/mongo.rb +2 -0
  6. data/lib/mongo/address.rb +4 -0
  7. data/lib/mongo/address/validator.rb +99 -0
  8. data/lib/mongo/auth.rb +7 -2
  9. data/lib/mongo/auth/user.rb +1 -7
  10. data/lib/mongo/background_thread.rb +135 -0
  11. data/lib/mongo/bulk_write/transformable.rb +3 -3
  12. data/lib/mongo/client.rb +74 -16
  13. data/lib/mongo/cluster.rb +193 -41
  14. data/lib/mongo/cluster/periodic_executor.rb +31 -43
  15. data/lib/mongo/cluster/sdam_flow.rb +26 -3
  16. data/lib/mongo/cluster/srv_monitor.rb +127 -0
  17. data/lib/mongo/collection/view/readable.rb +3 -5
  18. data/lib/mongo/collection/view/writable.rb +3 -3
  19. data/lib/mongo/cursor/builder/get_more_command.rb +1 -4
  20. data/lib/mongo/cursor/builder/kill_cursors_command.rb +5 -23
  21. data/lib/mongo/cursor/builder/op_get_more.rb +2 -2
  22. data/lib/mongo/cursor/builder/op_kill_cursors.rb +5 -24
  23. data/lib/mongo/error.rb +1 -0
  24. data/lib/mongo/error/auth_error.rb +1 -1
  25. data/lib/mongo/error/connection_check_out_timeout.rb +7 -8
  26. data/lib/mongo/error/invalid_address.rb +24 -0
  27. data/lib/mongo/error/notable.rb +2 -2
  28. data/lib/mongo/error/operation_failure.rb +3 -3
  29. data/lib/mongo/error/pool_closed_error.rb +11 -4
  30. data/lib/mongo/event.rb +1 -1
  31. data/lib/mongo/grid/file.rb +0 -5
  32. data/lib/mongo/grid/file/chunk.rb +0 -2
  33. data/lib/mongo/grid/fs_bucket.rb +13 -15
  34. data/lib/mongo/grid/stream/write.rb +3 -9
  35. data/lib/mongo/loggable.rb +5 -1
  36. data/lib/mongo/monitoring.rb +1 -0
  37. data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +7 -0
  38. data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +11 -3
  39. data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +11 -3
  40. data/lib/mongo/monitoring/event/cmap/pool_closed.rb +11 -3
  41. data/lib/mongo/monitoring/event/cmap/pool_created.rb +12 -3
  42. data/lib/mongo/monitoring/unified_sdam_log_subscriber.rb +62 -0
  43. data/lib/mongo/operation/shared/executable.rb +5 -10
  44. data/lib/mongo/operation/shared/sessions_supported.rb +1 -5
  45. data/lib/mongo/protocol/get_more.rb +1 -2
  46. data/lib/mongo/protocol/kill_cursors.rb +13 -6
  47. data/lib/mongo/protocol/serializers.rb +4 -20
  48. data/lib/mongo/retryable.rb +9 -34
  49. data/lib/mongo/semaphore.rb +1 -1
  50. data/lib/mongo/server.rb +113 -42
  51. data/lib/mongo/server/connection.rb +12 -5
  52. data/lib/mongo/server/connection_pool.rb +250 -40
  53. data/lib/mongo/server/connection_pool/populator.rb +58 -0
  54. data/lib/mongo/server/description.rb +9 -2
  55. data/lib/mongo/server/monitor.rb +68 -93
  56. data/lib/mongo/server/monitor/connection.rb +2 -0
  57. data/lib/mongo/server_selector/selectable.rb +13 -5
  58. data/lib/mongo/session.rb +0 -13
  59. data/lib/mongo/srv.rb +17 -0
  60. data/lib/mongo/srv/monitor.rb +96 -0
  61. data/lib/mongo/srv/resolver.rb +130 -0
  62. data/lib/mongo/srv/result.rb +126 -0
  63. data/lib/mongo/srv/warning_result.rb +35 -0
  64. data/lib/mongo/uri.rb +45 -55
  65. data/lib/mongo/uri/srv_protocol.rb +89 -42
  66. data/lib/mongo/version.rb +1 -1
  67. data/mongo.gemspec +3 -4
  68. data/spec/README.md +6 -1
  69. data/spec/enterprise_auth/kerberos_spec.rb +7 -6
  70. data/spec/integration/change_stream_examples_spec.rb +0 -4
  71. data/spec/integration/client_construction_spec.rb +14 -2
  72. data/spec/integration/connect_single_rs_name_spec.rb +2 -2
  73. data/spec/integration/connection_pool_populator_spec.rb +296 -0
  74. data/spec/integration/connection_spec.rb +31 -22
  75. data/spec/integration/cursor_reaping_spec.rb +1 -2
  76. data/spec/integration/docs_examples_spec.rb +0 -4
  77. data/spec/integration/heartbeat_events_spec.rb +17 -15
  78. data/spec/integration/reconnect_spec.rb +144 -1
  79. data/spec/integration/retryable_writes_errors_spec.rb +0 -4
  80. data/spec/integration/retryable_writes_spec.rb +36 -36
  81. data/spec/integration/sdam_error_handling_spec.rb +31 -25
  82. data/spec/integration/sdam_events_spec.rb +2 -6
  83. data/spec/integration/server_monitor_spec.rb +28 -0
  84. data/spec/integration/server_selector_spec.rb +7 -5
  85. data/spec/integration/srv_monitoring_spec.rb +360 -0
  86. data/spec/integration/step_down_spec.rb +4 -6
  87. data/spec/lite_spec_helper.rb +22 -0
  88. data/spec/mongo/address/validator_spec.rb +51 -0
  89. data/spec/mongo/auth/cr_spec.rb +1 -29
  90. data/spec/mongo/auth/ldap_spec.rb +1 -29
  91. data/spec/mongo/auth/scram/conversation_spec.rb +0 -2
  92. data/spec/mongo/auth/scram/negotiation_spec.rb +1 -1
  93. data/spec/mongo/auth/scram_spec.rb +1 -29
  94. data/spec/mongo/auth/user/view_spec.rb +1 -36
  95. data/spec/mongo/auth/user_spec.rb +0 -12
  96. data/spec/mongo/auth/x509_spec.rb +1 -29
  97. data/spec/mongo/bulk_write_spec.rb +2 -2
  98. data/spec/mongo/client_construction_spec.rb +56 -15
  99. data/spec/mongo/client_spec.rb +31 -27
  100. data/spec/mongo/cluster/periodic_executor_spec.rb +16 -0
  101. data/spec/mongo/cluster/srv_monitor_spec.rb +214 -0
  102. data/spec/mongo/cluster/topology/replica_set_spec.rb +16 -11
  103. data/spec/mongo/cluster/topology/sharded_spec.rb +12 -9
  104. data/spec/mongo/cluster/topology/single_spec.rb +20 -11
  105. data/spec/mongo/cluster_spec.rb +45 -29
  106. data/spec/mongo/collection/view/map_reduce_spec.rb +14 -9
  107. data/spec/mongo/collection/view/readable_spec.rb +0 -16
  108. data/spec/mongo/collection_spec.rb +0 -44
  109. data/spec/mongo/cursor/builder/get_more_command_spec.rb +2 -4
  110. data/spec/mongo/cursor/builder/op_get_more_spec.rb +2 -4
  111. data/spec/mongo/cursor_spec.rb +27 -7
  112. data/spec/mongo/monitoring/event/cmap/connection_checked_in_spec.rb +10 -3
  113. data/spec/mongo/monitoring/event/cmap/connection_checked_out_spec.rb +10 -3
  114. data/spec/mongo/monitoring/event/cmap/pool_closed_spec.rb +10 -3
  115. data/spec/mongo/monitoring/event/cmap/pool_created_spec.rb +10 -3
  116. data/spec/mongo/operation/delete/op_msg_spec.rb +17 -8
  117. data/spec/mongo/operation/insert/op_msg_spec.rb +50 -35
  118. data/spec/mongo/operation/update/op_msg_spec.rb +14 -7
  119. data/spec/mongo/retryable_spec.rb +52 -31
  120. data/spec/mongo/server/app_metadata_spec.rb +0 -8
  121. data/spec/mongo/server/connection_auth_spec.rb +5 -2
  122. data/spec/mongo/server/connection_pool/populator_spec.rb +101 -0
  123. data/spec/mongo/server/connection_pool_spec.rb +256 -107
  124. data/spec/mongo/server/connection_spec.rb +22 -33
  125. data/spec/mongo/server/description_spec.rb +42 -4
  126. data/spec/mongo/server/monitor/connection_spec.rb +22 -11
  127. data/spec/mongo/server/monitor_spec.rb +66 -107
  128. data/spec/mongo/server_spec.rb +82 -60
  129. data/spec/mongo/session/session_pool_spec.rb +1 -5
  130. data/spec/mongo/session_spec.rb +0 -4
  131. data/spec/mongo/socket/ssl_spec.rb +2 -2
  132. data/spec/mongo/srv/monitor_spec.rb +211 -0
  133. data/spec/mongo/srv/result_spec.rb +54 -0
  134. data/spec/mongo/uri/srv_protocol_spec.rb +30 -15
  135. data/spec/mongo/uri_spec.rb +125 -4
  136. data/spec/spec_helper.rb +6 -0
  137. data/spec/spec_tests/auth_spec.rb +39 -0
  138. data/spec/spec_tests/cmap_spec.rb +55 -8
  139. data/spec/spec_tests/connection_string_spec.rb +6 -31
  140. data/spec/spec_tests/data/auth/connection-string.yml +297 -0
  141. data/spec/spec_tests/data/cmap/pool-checkout-error-closed.yml +4 -1
  142. data/spec/spec_tests/data/cmap/pool-create-with-options.yml +1 -0
  143. data/spec/spec_tests/data/command_monitoring/insertMany.yml +1 -1
  144. data/spec/spec_tests/data/connection_string/invalid-uris.yml +20 -0
  145. data/spec/spec_tests/data/connection_string/valid-auth.yml +16 -0
  146. data/spec/spec_tests/data/connection_string/valid-warnings.yml +26 -30
  147. data/spec/spec_tests/data/transactions/abort.yml +3 -3
  148. data/spec/spec_tests/data/transactions/error-labels.yml +3 -3
  149. data/spec/spec_tests/data/transactions_api/callback-retry.yml +3 -3
  150. data/spec/spec_tests/data/uri_options/auth-options.yml +1 -1
  151. data/spec/spec_tests/max_staleness_spec.rb +7 -2
  152. data/spec/spec_tests/retryable_reads_spec.rb +0 -31
  153. data/spec/spec_tests/sdam_monitoring_spec.rb +12 -12
  154. data/spec/spec_tests/sdam_spec.rb +4 -7
  155. data/spec/spec_tests/server_selection_spec.rb +6 -2
  156. data/spec/spec_tests/transactions_spec.rb +0 -2
  157. data/spec/spec_tests/uri_options_spec.rb +4 -2
  158. data/spec/stress/connection_pool_stress_spec.rb +203 -0
  159. data/spec/stress/connection_pool_timing_spec.rb +181 -0
  160. data/spec/support/auth.rb +113 -0
  161. data/spec/support/background_thread_registry.rb +63 -0
  162. data/spec/support/client_registry.rb +11 -2
  163. data/spec/support/cluster_config.rb +65 -46
  164. data/spec/support/cluster_tools.rb +2 -2
  165. data/spec/support/cmap.rb +13 -14
  166. data/spec/support/cmap/verifier.rb +4 -5
  167. data/spec/support/command_monitoring.rb +0 -5
  168. data/spec/support/common_shortcuts.rb +101 -1
  169. data/spec/support/constraints.rb +25 -0
  170. data/spec/support/dns.rb +13 -0
  171. data/spec/support/event_subscriber.rb +0 -7
  172. data/spec/support/json_ext_formatter.rb +5 -1
  173. data/spec/support/lite_constraints.rb +22 -6
  174. data/spec/support/local_resource_registry.rb +34 -0
  175. data/spec/support/sdam_monitoring.rb +115 -0
  176. data/spec/support/spec_config.rb +20 -6
  177. data/spec/support/spec_setup.rb +2 -2
  178. data/spec/support/transactions.rb +1 -1
  179. data/spec/support/transactions/test.rb +1 -1
  180. data/spec/support/utils.rb +1 -16
  181. metadata +685 -659
  182. metadata.gz.sig +0 -0
  183. data/lib/mongo/event/description_changed.rb +0 -52
  184. data/spec/integration/bson_symbol_spec.rb +0 -34
  185. data/spec/integration/crud_spec.rb +0 -45
  186. data/spec/integration/get_more_spec.rb +0 -32
  187. data/spec/integration/grid_fs_bucket_spec.rb +0 -48
  188. data/spec/integration/retryable_errors_spec.rb +0 -265
  189. data/spec/integration/size_limit_spec.rb~12e1e9c4f... RUBY-2242 Fix zlib compression (#2021) +0 -98
  190. data/spec/mongo/cursor/builder/op_kill_cursors_spec.rb +0 -56
  191. data/spec/runners/sdam/verifier.rb +0 -88
@@ -0,0 +1,96 @@
1
+ # Copyright (C) 2014-2019 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ module SRV
17
+
18
+ # Polls SRV records for the URI that a cluster was created for and
19
+ # updates the list of servers in the cluster when records change.
20
+ #
21
+ # @api private
22
+ class Monitor
23
+ include Loggable
24
+
25
+ MIN_RESCAN_FREQUENCY = 60
26
+
27
+ attr_reader :options
28
+
29
+ def initialize(cluster, resolver, srv_records, options = nil)
30
+ @options = options || {}
31
+ @cluster = cluster
32
+ @resolver = resolver
33
+ @records = srv_records
34
+ @no_records_found = false
35
+ end
36
+
37
+ def start_monitor!
38
+ @thread = Thread.new do
39
+ loop do
40
+ sleep(rescan_frequency)
41
+ scan!
42
+ end
43
+ end
44
+
45
+ ObjectSpace.define_finalizer(self, self.class.finalize(@thread))
46
+ end
47
+
48
+ def scan!
49
+ @old_hosts = @records.hosts
50
+
51
+ begin
52
+ @records = @resolver.get_records(@records.hostname)
53
+ rescue Resolv::ResolvTimeout => e
54
+ log_warn("Timed out trying to resolve hostname #{@records.hostname}")
55
+ return
56
+ rescue Resolv::ResolvError => e
57
+ log_warn("Unable to resolve hostname #{@records.hostname}")
58
+ return
59
+ end
60
+
61
+ if @records.empty?
62
+ @no_records_found = true
63
+ return
64
+ end
65
+
66
+ @no_records_found = false
67
+
68
+ (@old_hosts - @records.hosts).each do |host|
69
+ @cluster.remove(host)
70
+ end
71
+
72
+ (@records.hosts - @old_hosts).each do |host|
73
+ @cluster.add(host)
74
+ end
75
+ end
76
+
77
+ def self.finalize(thread)
78
+ Proc.new do
79
+ thread.kill
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def rescan_frequency
86
+ if @no_records_found
87
+ Server:: Monitor::HEARTBEAT_FREQUENCY
88
+ elsif @records.min_ttl.nil?
89
+ MIN_RESCAN_FREQUENCY
90
+ else
91
+ [@records.min_ttl, MIN_RESCAN_FREQUENCY].max
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,130 @@
1
+ # Copyright (C) 2017-2019 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ module Srv
17
+
18
+ # Encapsulates the necessary behavior for querying SRV records as
19
+ # required by the driver.
20
+ #
21
+ # @api private
22
+ class Resolver
23
+ include Loggable
24
+
25
+ # @return [ String ] RECORD_PREFIX The prefix prepended to each hostname
26
+ # before querying SRV records.
27
+ RECORD_PREFIX = '_mongodb._tcp.'.freeze
28
+
29
+ # Creates a new Resolver.
30
+ #
31
+ # @param [ Hash ] options The options for the resolver.
32
+ #
33
+ # @option options [ Boolean ] :raise_on_invalid Whether or not to raise
34
+ # an exception if either a record with a mismatched domain is found
35
+ # or if no records are found. Defaults to true.
36
+ # @option options [ Hash ] :resolv_options For internal driver use only.
37
+ # Options to pass through to Resolv::DNS constructor for SRV lookups.
38
+ def initialize(options = nil)
39
+ @options = if options
40
+ options.dup
41
+ else
42
+ {}
43
+ end.freeze
44
+ @resolver = Resolv::DNS.new(@options[:resolv_options])
45
+ end
46
+
47
+ # Obtains all of the SRV records for a given hostname.
48
+ #
49
+ # In the event that a record with a mismatched domain is found or no
50
+ # records are found, if the :raise_on_invalid option is true,
51
+ # an exception will be raised, otherwise a warning will be logged.
52
+ #
53
+ # @param [ String ] hostname The hostname whose records should be obtained.
54
+ #
55
+ # @raise [ Mongo::Error::MismatchedDomain ] If the :raise_in_invalid
56
+ # Resolver option is true and a record with a domain name that does
57
+ # not match the hostname's is found.
58
+ # @raise [ Mongo::Error::NoSRVRecords ] If the :raise_in_invalid Resolver
59
+ # option is true and no records are found.
60
+ #
61
+ # @return [ Mongo::Srv::Result ] SRV lookup result.
62
+ def get_records(hostname)
63
+ query_name = RECORD_PREFIX + hostname
64
+ resources = @resolver.getresources(query_name, Resolv::DNS::Resource::IN::SRV)
65
+
66
+ # Collect all of the records into a Result object, raising an error
67
+ # or logging a warning if a record with a mismatched domain is found.
68
+ # Note that in the case a warning is raised, the record is _not_
69
+ # added to the Result object.
70
+ result = Srv::Result.new(hostname)
71
+ resources.each do |record|
72
+ begin
73
+ result.add_record(record)
74
+ rescue Error::MismatchedDomain => e
75
+ if raise_on_invalid?
76
+ raise
77
+ else
78
+ log_warn(e.message)
79
+ end
80
+ end
81
+ end
82
+
83
+ # If no records are found, either raise an error or log a warning
84
+ # based on the Resolver's :raise_on_invalid option.
85
+ if result.empty?
86
+ if raise_on_invalid?
87
+ raise Error::NoSRVRecords.new(URI::SRVProtocol::NO_SRV_RECORDS % hostname)
88
+ else
89
+ log_warn(URI::SRVProtocol::NO_SRV_RECORDS % hostname)
90
+ end
91
+ end
92
+
93
+ result
94
+ end
95
+
96
+ # Obtains the TXT records of a host.
97
+ #
98
+ # @param [ String ] hostname The host whose TXT records should be obtained.
99
+ #
100
+ # @return [ nil | String ] URI options string from TXT record
101
+ # associated with the hostname, or nil if there is no such record.
102
+ #
103
+ # @raise [ Mongo::Error::InvalidTXTRecord ] If more than one TXT record is found.
104
+ def get_txt_options_string(hostname)
105
+ records = @resolver.getresources(hostname, Resolv::DNS::Resource::IN::TXT)
106
+ if records.empty?
107
+ return nil
108
+ end
109
+
110
+ if records.length > 1
111
+ msg = "Only one TXT record is allowed: querying hostname #{hostname} returned #{records.length} records"
112
+
113
+ raise Error::InvalidTXTRecord, msg
114
+ end
115
+
116
+ records[0].strings.join
117
+ end
118
+
119
+ private
120
+
121
+ # Checks whether an error should be raised due to either a record with
122
+ # a mismatched domain being found or no records being found.
123
+ #
124
+ # @return [ Boolean ] Whether an error should be raised.
125
+ def raise_on_invalid?
126
+ @raise_on_invalid ||= @options[:raise_on_invalid] || true
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,126 @@
1
+ # Copyright (C) 2017-2019 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+
17
+ module Srv
18
+
19
+ # SRV record lookup result.
20
+ #
21
+ # Contains server addresses that the query resolved to, and minimum TTL
22
+ # of the DNS records.
23
+ #
24
+ # @api private
25
+ class Result
26
+ include Address::Validator
27
+
28
+ # @return [ String ] MISMATCHED_DOMAINNAME Error message format string indicating that an SRV
29
+ # record found does not match the domain of a hostname.
30
+ MISMATCHED_DOMAINNAME = "Parent domain name in SRV record result (%s) does not match " +
31
+ "that of the hostname (%s)".freeze
32
+
33
+ # @return [ String ] query_hostname The hostname pointing to the DNS records.
34
+ attr_reader :query_hostname
35
+
36
+ # @return [ Array<String> ] address_strs The host strings of the SRV records
37
+ # for the query hostname.
38
+ attr_reader :address_strs
39
+
40
+ # @return [ Integer | nil ] min_ttl The smallest TTL found among the
41
+ # records (or nil if no records have been added).
42
+ attr_accessor :min_ttl
43
+
44
+ # Create a new object to keep track of the SRV records of the hostname.
45
+ #
46
+ # @param [ String ] hostname The hostname pointing to the DNS records.
47
+ def initialize(hostname)
48
+ @query_hostname = hostname
49
+ @address_strs = []
50
+ @min_ttl = nil
51
+ end
52
+
53
+ # Checks whether there are any records.
54
+ #
55
+ # @return [ Boolean ] Whether or not there are any records.
56
+ def empty?
57
+ @address_strs.empty?
58
+ end
59
+
60
+ # Adds a new record.
61
+ #
62
+ # @param [ Resolv::DNS::Resource ] record An SRV record found for the hostname.
63
+ def add_record(record)
64
+ record_host = normalize_hostname(record.target.to_s)
65
+ port = record.port
66
+ validate_hostname!(record_host)
67
+ validate_same_origin!(record_host)
68
+ address_str = if record_host.index(':')
69
+ # IPV6 address
70
+ "[#{record_host}]:#{port}"
71
+ else
72
+ "#{record_host}:#{port}"
73
+ end
74
+ @address_strs << address_str
75
+
76
+ if @min_ttl.nil?
77
+ @min_ttl = record.ttl
78
+ else
79
+ @min_ttl = [@min_ttl, record.ttl].min
80
+ end
81
+
82
+ nil
83
+ end
84
+
85
+ private
86
+
87
+ # Transforms the provided hostname to simplify its validation later on.
88
+ #
89
+ # This method is safe to call during both initial DNS seed list discovery
90
+ # and during SRV monitoring, in that it does not convert invalid hostnames
91
+ # into valid ones.
92
+ #
93
+ # - Converts the hostname to lower case.
94
+ # - Removes one trailing dot, if there is exactly one. If the hostname
95
+ # has multiple trailing dots, it is unchanged.
96
+ #
97
+ # @param [ String ] host Hostname to transform.
98
+ def normalize_hostname(host)
99
+ host = host.downcase
100
+ unless host.end_with?('..')
101
+ host = host.sub(/\.\z/, '')
102
+ end
103
+ host
104
+ end
105
+
106
+ # Ensures that a record's domain name matches that of the hostname.
107
+ #
108
+ # A hostname's domain name consists of each of the '.' delineated
109
+ # parts after the first. For example, the hostname 'foo.bar.baz'
110
+ # has the domain name 'bar.baz'.
111
+ #
112
+ # @param [ String ] record_host The host of the SRV record.
113
+ #
114
+ # @raise [ Mongo::Error::MismatchedDomain ] If the record's domain name doesn't match that of
115
+ # the hostname.
116
+ def validate_same_origin!(record_host)
117
+ domain_name ||= query_hostname.split('.')[1..-1]
118
+ host_parts = record_host.split('.')
119
+
120
+ unless (host_parts.size > domain_name.size) && (domain_name == host_parts[-domain_name.length..-1])
121
+ raise Error::MismatchedDomain.new(MISMATCHED_DOMAINNAME % [record_host, domain_name])
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright (C) 2019 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the 'License');
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an 'AS IS' BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+
17
+ module Srv
18
+
19
+ # SRV record lookup result which warns on errors rather than raising
20
+ # exceptions.
21
+ #
22
+ # @api private
23
+ class WarningResult < Result
24
+
25
+ # Adds a new record.
26
+ #
27
+ # @param [ Resolv::DNS::Resource ] record An SRV record found for the hostname.
28
+ def add_record(record)
29
+ super
30
+ rescue Error::InvalidAddress, Error::MismatchedDomain => e
31
+ log_warn(e.message)
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/mongo/uri.rb CHANGED
@@ -28,13 +28,14 @@ module Mongo
28
28
  # @since 2.0.0
29
29
  class URI
30
30
  include Loggable
31
+ include Address::Validator
31
32
 
32
33
  # The uri parser object options.
33
34
  #
34
35
  # @since 2.0.0
35
36
  attr_reader :options
36
37
 
37
- # The options specified in the uri.
38
+ # Mongo::Options::Redacted of the options specified in the uri.
38
39
  #
39
40
  # @since 2.1.0
40
41
  attr_reader :uri_options
@@ -64,6 +65,7 @@ module Mongo
64
65
  # Error details for an invalid scheme.
65
66
  #
66
67
  # @since 2.1.0
68
+ # @deprecated
67
69
  INVALID_SCHEME = "Invalid scheme. Scheme must be '#{MONGODB_SCHEME}' or '#{MONGODB_SRV_SCHEME}'".freeze
68
70
 
69
71
  # MongoDB URI format specification.
@@ -80,7 +82,7 @@ module Mongo
80
82
  # Unsafe characters that must be urlencoded.
81
83
  #
82
84
  # @since 2.1.0
83
- UNSAFE = /[\:\/\+\@]/
85
+ UNSAFE = /[\:\/\@]/
84
86
 
85
87
  # Percent sign that must be encoded in user creds.
86
88
  #
@@ -224,7 +226,7 @@ module Mongo
224
226
  when MONGODB_SRV_SCHEME
225
227
  SRVProtocol.new(string, opts)
226
228
  else
227
- raise Error::InvalidURI.new(string, INVALID_SCHEME)
229
+ raise Error::InvalidURI.new(string, "Invalid scheme '#{scheme}'. Scheme must be '#{MONGODB_SCHEME}' or '#{MONGODB_SRV_SCHEME}'")
228
230
  end
229
231
  end
230
232
 
@@ -235,14 +237,18 @@ module Mongo
235
237
  # @example Get the client options.
236
238
  # uri.client_options
237
239
  #
238
- # @return [ Hash ] The options passed to the Mongo::Client
240
+ # @return [ Mongo::Options::Redacted ] The options passed to the Mongo::Client
239
241
  #
240
242
  # @since 2.0.0
241
243
  def client_options
242
- opts = uri_options.merge(:database => database)
244
+ opts = default_client_options.merge(uri_options)
243
245
  @user ? opts.merge(credentials) : opts
244
246
  end
245
247
 
248
+ def srv_records
249
+ nil
250
+ end
251
+
246
252
  # Create the new uri from the provided string.
247
253
  #
248
254
  # @example Create the new URI.
@@ -258,7 +264,9 @@ module Mongo
258
264
  @string = string
259
265
  @options = options
260
266
  parsed_scheme, _, remaining = string.partition(SCHEME_DELIM)
261
- raise_invalid_error!(INVALID_SCHEME) unless parsed_scheme == scheme
267
+ unless parsed_scheme == scheme
268
+ raise_invalid_error!("Invalid scheme '#{parsed_scheme}'. Scheme must be '#{MONGODB_SCHEME}'. Use URI#get to parse SRV URIs.")
269
+ end
262
270
  if remaining.empty?
263
271
  raise_invalid_error!('No hosts in the URI')
264
272
  end
@@ -359,7 +367,7 @@ module Mongo
359
367
  raise_invalid_error!('Empty host given in the host list')
360
368
  end
361
369
  decode(host).tap do |host|
362
- validate_host!(host)
370
+ validate_address_str!(host)
363
371
  end
364
372
  end
365
373
 
@@ -369,6 +377,8 @@ module Mongo
369
377
  if db
370
378
  @database = parse_database!(db)
371
379
  end
380
+ rescue Error::InvalidAddress => e
381
+ raise_invalid_error!(e.message)
372
382
  end
373
383
 
374
384
  def extract_db_opts!(string)
@@ -405,14 +415,12 @@ module Mongo
405
415
 
406
416
  def parse_user!(string)
407
417
  if (string && user = string.partition(AUTH_USER_PWD_DELIM)[0])
408
- if user.length > 0
409
- raise_invalid_error!(UNESCAPED_USER_PWD) if user =~ UNSAFE
410
- user_decoded = decode(user)
411
- if user_decoded =~ PERCENT_CHAR && encode(user_decoded) != user
412
- raise_invalid_error!(UNESCAPED_USER_PWD)
413
- end
414
- user_decoded
418
+ raise_invalid_error!(UNESCAPED_USER_PWD) if user =~ UNSAFE
419
+ user_decoded = decode(user)
420
+ if user_decoded =~ PERCENT_CHAR && encode(user_decoded) != user
421
+ raise_invalid_error!(UNESCAPED_USER_PWD)
415
422
  end
423
+ user_decoded
416
424
  end
417
425
  end
418
426
 
@@ -434,50 +442,32 @@ module Mongo
434
442
  decode(string) if string.length > 0
435
443
  end
436
444
 
437
- def validate_port_string!(port)
438
- unless port.nil? || (port.length > 0 && port.to_i > 0 && port.to_i <= 65535)
439
- raise_invalid_error!(INVALID_PORT)
445
+ def default_client_options
446
+ opts = Options::Redacted.new(database: database)
447
+
448
+ if @uri_options[:auth_mech] || @user
449
+ opts[:auth_source] = default_auth_source
440
450
  end
441
- end
442
451
 
443
- # Takes a host in ipv4/ipv6/hostname/socket path format and validates
444
- # its format.
445
- def validate_host!(host)
446
- case host
447
- when /\A\[[\d:]+\](?::(\d+))?\z/
448
- # ipv6 with optional port
449
- if port_str = $1
450
- validate_port_string!(port_str)
451
- end
452
- when /\A\//, /\.sock\z/
453
- # Unix socket path.
454
- # Spec requires us to validate that the path has no unescaped
455
- # slashes, but if this were to be the case, parsing would have
456
- # already failed elsewhere because the URI would've been split in
457
- # a weird place.
458
- # The spec also allows relative socket paths and requires that
459
- # socket paths end in ".sock". We accept all paths but special case
460
- # the .sock extension to avoid relative paths falling into the
461
- # host:port case below.
462
- when /[\/\[\]]/
463
- # Not a host:port nor an ipv4 address with optional port.
464
- # Possibly botched ipv6 address with e.g. port delimiter present and
465
- # port missing, or extra junk before or after.
466
- raise_invalid_error!("Invalid hostname: #{host}")
467
- when /:.*:/m
468
- raise_invalid_error!("Multiple port delimiters are not allowed: #{host}")
469
- else
470
- # host:port or ipv4 address with optional port number
471
- host, port = host.split(':')
472
- if host.empty?
473
- raise_invalid_error!("Host is empty: #{host}")
474
- end
452
+ if @uri_options[:auth_mech] == :gssapi
453
+ opts[:auth_mech_properties] = default_auth_mech_properties
454
+ end
475
455
 
476
- if port && port.empty?
477
- raise_invalid_error!("Port is empty: #{port}")
478
- end
456
+ opts
457
+ end
458
+
459
+ def default_auth_mech_properties
460
+ { service_name: 'mongodb' }
461
+ end
479
462
 
480
- validate_port_string!(port)
463
+ def default_auth_source
464
+ case @uri_options[:auth_mech]
465
+ when :gssapi, :mongodb_x509
466
+ :external
467
+ when :plain
468
+ @database || :external
469
+ else
470
+ @database || Database::ADMIN
481
471
  end
482
472
  end
483
473
 
@@ -490,7 +480,7 @@ module Mongo
490
480
  end
491
481
 
492
482
  def decode(value)
493
- ::URI::DEFAULT_PARSER.unescape(value)
483
+ ::URI.decode(value)
494
484
  end
495
485
 
496
486
  def encode(value)