line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Copyright 2014 - present 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
|
64
|
|
|
64
|
|
95282
|
use strict; |
|
64
|
|
|
|
|
178
|
|
|
64
|
|
|
|
|
2075
|
|
16
|
64
|
|
|
64
|
|
384
|
use warnings; |
|
64
|
|
|
|
|
160
|
|
|
64
|
|
|
|
|
2164
|
|
17
|
|
|
|
|
|
|
package MongoDB::_URI; |
18
|
|
|
|
|
|
|
|
19
|
64
|
|
|
64
|
|
2051
|
use version; |
|
64
|
|
|
|
|
7621
|
|
|
64
|
|
|
|
|
480
|
|
20
|
|
|
|
|
|
|
our $VERSION = 'v2.2.1'; |
21
|
|
|
|
|
|
|
|
22
|
64
|
|
|
64
|
|
7416
|
use Moo; |
|
64
|
|
|
|
|
32127
|
|
|
64
|
|
|
|
|
430
|
|
23
|
64
|
|
|
64
|
|
28496
|
use MongoDB::Error; |
|
64
|
|
|
|
|
198
|
|
|
64
|
|
|
|
|
8375
|
|
24
|
64
|
|
|
64
|
|
3005
|
use Encode (); |
|
64
|
|
|
|
|
42851
|
|
|
64
|
|
|
|
|
1611
|
|
25
|
64
|
|
|
64
|
|
2482
|
use Time::HiRes qw(time); |
|
64
|
|
|
|
|
5548
|
|
|
64
|
|
|
|
|
709
|
|
26
|
64
|
|
|
64
|
|
8905
|
use MongoDB::_Constants qw( RESCAN_SRV_FREQUENCY_SEC ); |
|
64
|
|
|
|
|
163
|
|
|
64
|
|
|
|
|
3870
|
|
27
|
64
|
|
|
|
|
668
|
use Types::Standard qw( |
28
|
|
|
|
|
|
|
Any |
29
|
|
|
|
|
|
|
ArrayRef |
30
|
|
|
|
|
|
|
HashRef |
31
|
|
|
|
|
|
|
Str |
32
|
|
|
|
|
|
|
Int |
33
|
|
|
|
|
|
|
Num |
34
|
64
|
|
|
64
|
|
465
|
); |
|
64
|
|
|
|
|
159
|
|
35
|
64
|
|
|
64
|
|
77076
|
use namespace::clean -except => 'meta'; |
|
64
|
|
|
|
|
164
|
|
|
64
|
|
|
|
|
578
|
|
36
|
64
|
|
|
64
|
|
49709
|
use Scalar::Util qw/looks_like_number/; |
|
64
|
|
|
|
|
171
|
|
|
64
|
|
|
|
|
281043
|
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $uri_re = |
39
|
|
|
|
|
|
|
qr{ |
40
|
|
|
|
|
|
|
mongodb(?:\+srv|):// |
41
|
|
|
|
|
|
|
(?: ([^:]*) (?: : ([^@]*) )? @ )? # [username(:password)?@] |
42
|
|
|
|
|
|
|
([^/?]*) # host1[:port1][,host2[:port2],...[,hostN[:portN]]] |
43
|
|
|
|
|
|
|
(?: |
44
|
|
|
|
|
|
|
/ ([^?]*) # /[database] |
45
|
|
|
|
|
|
|
(?: [?] (.*) )? # [?options] |
46
|
|
|
|
|
|
|
)? |
47
|
|
|
|
|
|
|
}x; |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
my %options_with_list_type = map { lc($_) => 1 } qw( |
50
|
|
|
|
|
|
|
readPreferenceTags |
51
|
|
|
|
|
|
|
); |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
has uri => ( |
54
|
|
|
|
|
|
|
is => 'ro', |
55
|
|
|
|
|
|
|
isa => Str, |
56
|
|
|
|
|
|
|
required => 1, |
57
|
|
|
|
|
|
|
); |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
has username => ( |
60
|
|
|
|
|
|
|
is => 'ro', |
61
|
|
|
|
|
|
|
isa => Any, |
62
|
|
|
|
|
|
|
writer => '_set_username', |
63
|
|
|
|
|
|
|
); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
has password => ( |
66
|
|
|
|
|
|
|
is => 'ro', |
67
|
|
|
|
|
|
|
isa => Any, |
68
|
|
|
|
|
|
|
writer => '_set_password', |
69
|
|
|
|
|
|
|
); |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
has db_name => ( |
72
|
|
|
|
|
|
|
is => 'ro', |
73
|
|
|
|
|
|
|
isa => Str, |
74
|
|
|
|
|
|
|
writer => '_set_db_name', |
75
|
|
|
|
|
|
|
default => '', |
76
|
|
|
|
|
|
|
); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
has options => ( |
79
|
|
|
|
|
|
|
is => 'ro', |
80
|
|
|
|
|
|
|
isa => HashRef, |
81
|
|
|
|
|
|
|
writer => '_set_options', |
82
|
|
|
|
|
|
|
default => sub { {} }, |
83
|
|
|
|
|
|
|
); |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
has hostids => ( |
86
|
|
|
|
|
|
|
is => 'ro', |
87
|
|
|
|
|
|
|
isa => ArrayRef, |
88
|
|
|
|
|
|
|
writer => '_set_hostids', |
89
|
|
|
|
|
|
|
default => sub { [] }, |
90
|
|
|
|
|
|
|
); |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
has valid_options => ( |
93
|
|
|
|
|
|
|
is => 'lazy', |
94
|
|
|
|
|
|
|
isa => HashRef, |
95
|
|
|
|
|
|
|
); |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
has expires => ( |
98
|
|
|
|
|
|
|
is => 'ro', |
99
|
|
|
|
|
|
|
isa => Num, |
100
|
|
|
|
|
|
|
writer => '_set_expires', |
101
|
|
|
|
|
|
|
); |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub _build_valid_options { |
104
|
206
|
|
|
206
|
|
2102
|
my $self = shift; |
105
|
|
|
|
|
|
|
return { |
106
|
7210
|
|
|
|
|
24627
|
map { lc($_) => 1 } qw( |
107
|
|
|
|
|
|
|
appName |
108
|
|
|
|
|
|
|
authMechanism |
109
|
|
|
|
|
|
|
authMechanismProperties |
110
|
|
|
|
|
|
|
authSource |
111
|
|
|
|
|
|
|
compressors |
112
|
|
|
|
|
|
|
connect |
113
|
|
|
|
|
|
|
connectTimeoutMS |
114
|
|
|
|
|
|
|
heartbeatFrequencyMS |
115
|
|
|
|
|
|
|
journal |
116
|
|
|
|
|
|
|
localThresholdMS |
117
|
|
|
|
|
|
|
maxStalenessSeconds |
118
|
|
|
|
|
|
|
maxTimeMS |
119
|
|
|
|
|
|
|
readConcernLevel |
120
|
|
|
|
|
|
|
readPreference |
121
|
|
|
|
|
|
|
readPreferenceTags |
122
|
|
|
|
|
|
|
replicaSet |
123
|
|
|
|
|
|
|
serverSelectionTimeoutMS |
124
|
|
|
|
|
|
|
serverSelectionTryOnce |
125
|
|
|
|
|
|
|
socketCheckIntervalMS |
126
|
|
|
|
|
|
|
socketTimeoutMS |
127
|
|
|
|
|
|
|
tlsCAFile |
128
|
|
|
|
|
|
|
tlsCertificateKeyFile |
129
|
|
|
|
|
|
|
tlsCertificateKeyFilePassword |
130
|
|
|
|
|
|
|
w |
131
|
|
|
|
|
|
|
wTimeoutMS |
132
|
|
|
|
|
|
|
zlibCompressionLevel |
133
|
206
|
|
|
|
|
509
|
), keys %{ $self->_valid_str_to_bool_options } |
|
206
|
|
|
|
|
3349
|
|
134
|
|
|
|
|
|
|
}; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
has valid_srv_options => ( |
138
|
|
|
|
|
|
|
is => 'lazy', |
139
|
|
|
|
|
|
|
isa => HashRef, |
140
|
|
|
|
|
|
|
); |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub _build_valid_srv_options { |
143
|
|
|
|
|
|
|
return { |
144
|
0
|
|
|
0
|
|
0
|
map { lc($_) => 1 } qw( |
|
0
|
|
|
|
|
0
|
|
145
|
|
|
|
|
|
|
authSource |
146
|
|
|
|
|
|
|
replicaSet |
147
|
|
|
|
|
|
|
) |
148
|
|
|
|
|
|
|
}; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
has _valid_str_to_bool_options => ( |
152
|
|
|
|
|
|
|
is => 'lazy', |
153
|
|
|
|
|
|
|
isa => HashRef, |
154
|
|
|
|
|
|
|
builder => '_build_valid_str_to_bool_options', |
155
|
|
|
|
|
|
|
); |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
sub _build_valid_str_to_bool_options { |
158
|
|
|
|
|
|
|
return { |
159
|
206
|
|
|
206
|
|
2002
|
map { lc($_) => 1 } qw( |
|
1854
|
|
|
|
|
7660
|
|
160
|
|
|
|
|
|
|
journal |
161
|
|
|
|
|
|
|
retryReads |
162
|
|
|
|
|
|
|
retryWrites |
163
|
|
|
|
|
|
|
serverselectiontryonce |
164
|
|
|
|
|
|
|
ssl |
165
|
|
|
|
|
|
|
tls |
166
|
|
|
|
|
|
|
tlsAllowInvalidCertificates |
167
|
|
|
|
|
|
|
tlsAllowInvalidHostnames |
168
|
|
|
|
|
|
|
tlsInsecure |
169
|
|
|
|
|
|
|
) |
170
|
|
|
|
|
|
|
}; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
has _extra_options_validation => ( |
174
|
|
|
|
|
|
|
is => 'lazy', |
175
|
|
|
|
|
|
|
isa => HashRef, |
176
|
|
|
|
|
|
|
builder => '_build_extra_options_validation', |
177
|
|
|
|
|
|
|
); |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
sub _build_extra_options_validation { |
180
|
|
|
|
|
|
|
return { |
181
|
|
|
|
|
|
|
_PositiveInt => sub { |
182
|
26
|
|
|
26
|
|
56
|
my $v = shift; |
183
|
26
|
50
|
|
|
|
94
|
Int->($v) && $v >= 0; |
184
|
|
|
|
|
|
|
}, |
185
|
|
|
|
|
|
|
wtimeoutms => '_PositiveInt', |
186
|
|
|
|
|
|
|
connecttimeoutms => '_PositiveInt', |
187
|
|
|
|
|
|
|
localthresholdms => '_PositiveInt', |
188
|
|
|
|
|
|
|
serverselectiontimeoutms => '_PositiveInt', |
189
|
|
|
|
|
|
|
sockettimeoutms => '_PositiveInt', |
190
|
|
|
|
|
|
|
w => sub { |
191
|
19
|
|
|
19
|
|
42
|
my $v = shift; |
192
|
19
|
100
|
|
|
|
97
|
if (looks_like_number($v)) { |
193
|
15
|
|
|
|
|
75
|
return $v >= 0; |
194
|
|
|
|
|
|
|
} |
195
|
4
|
|
|
|
|
13
|
return 1; # or any string |
196
|
|
|
|
|
|
|
}, |
197
|
|
|
|
|
|
|
zlibcompressionlevel => sub { |
198
|
4
|
|
|
4
|
|
8
|
my $v = shift; |
199
|
4
|
100
|
66
|
|
|
13
|
Int->($v) && $v >= -1 && $v <= 9; |
200
|
|
|
|
|
|
|
}, |
201
|
|
|
|
|
|
|
heartbeatfrequencyms => sub { |
202
|
4
|
|
|
4
|
|
10
|
my $v = shift; |
203
|
4
|
50
|
|
|
|
16
|
Int->($v) && $v >= 500; |
204
|
|
|
|
|
|
|
}, |
205
|
|
|
|
|
|
|
maxstalenessseconds => sub { |
206
|
11
|
|
|
11
|
|
21
|
my $v = shift; |
207
|
11
|
50
|
100
|
|
|
47
|
Int->($v) && ( $v == 1 || $v == -1 || $v >= 90 ); |
|
|
|
100
|
|
|
|
|
208
|
|
|
|
|
|
|
}, |
209
|
151
|
|
|
151
|
|
8533
|
}; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
sub _unescape_all { |
213
|
1485
|
|
|
1485
|
|
2513
|
my $str = shift; |
214
|
1485
|
50
|
|
|
|
2837
|
return '' unless defined $str; |
215
|
1485
|
100
|
|
|
|
3531
|
if ( $str =~ s/%([0-9a-f]{2})/chr(hex($1))/ieg ) { |
|
156
|
|
|
|
|
662
|
|
216
|
70
|
|
|
|
|
241
|
$str = Encode::decode('UTF-8', $str); |
217
|
|
|
|
|
|
|
} |
218
|
1485
|
|
|
|
|
9303
|
return $str; |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
sub _parse_doc { |
222
|
9
|
|
|
9
|
|
23
|
my ($name, $string) = @_; |
223
|
9
|
|
|
|
|
19
|
my $set = {}; |
224
|
9
|
|
|
|
|
30
|
for my $tag ( split /,/, $string ) { |
225
|
12
|
50
|
|
|
|
64
|
if ( $tag =~ /\S/ ) { |
226
|
12
|
|
|
|
|
37
|
my @kv = map { my $s = $_; $s =~ s{^\s*}{}; $s =~ s{\s*$}{}; $s } split /:/, $tag, 2; |
|
23
|
|
|
|
|
40
|
|
|
23
|
|
|
|
|
60
|
|
|
23
|
|
|
|
|
84
|
|
|
23
|
|
|
|
|
55
|
|
227
|
12
|
100
|
|
|
|
38
|
if ( @kv != 2 ) { |
228
|
1
|
|
|
|
|
9
|
warn "in option '$name', '$tag' is not a key:value pair\n"; |
229
|
|
|
|
|
|
|
return |
230
|
1
|
|
|
|
|
7
|
} |
231
|
11
|
|
|
|
|
41
|
$set->{$kv[0]} = $kv[1]; |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
} |
234
|
8
|
|
|
|
|
21
|
return $set; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub _parse_options { |
238
|
208
|
|
|
208
|
|
5271
|
my ( $self, $valid, $result, $txt_record ) = @_; |
239
|
|
|
|
|
|
|
|
240
|
208
|
|
|
|
|
349
|
my %parsed; |
241
|
208
|
|
|
|
|
778
|
for my $opt ( split '&', $result->{options} ) { |
242
|
278
|
|
|
|
|
793
|
my @kv = split '=', $opt, -1; |
243
|
278
|
100
|
|
|
|
798
|
MongoDB::UsageError->throw("expected key value pair") unless @kv == 2; |
244
|
272
|
|
|
|
|
626
|
my ( $k, $v ) = map { _unescape_all($_) } @kv; |
|
544
|
|
|
|
|
935
|
|
245
|
|
|
|
|
|
|
# connection string spec calls for case normalization |
246
|
272
|
|
|
|
|
772
|
( my $lc_k = $k ) =~ tr[A-Z][a-z]; |
247
|
272
|
100
|
|
|
|
715
|
if ( !$valid->{$lc_k} ) { |
248
|
7
|
50
|
|
|
|
20
|
if ( $txt_record ) { |
249
|
0
|
|
|
|
|
0
|
MongoDB::Error->throw("Unsupported option '$k' in URI $self for TXT record $txt_record\n"); |
250
|
|
|
|
|
|
|
} else { |
251
|
7
|
|
|
|
|
39
|
warn "Unsupported option '$k' in URI $self\n"; |
252
|
|
|
|
|
|
|
} |
253
|
7
|
|
|
|
|
55
|
next; |
254
|
|
|
|
|
|
|
} |
255
|
265
|
100
|
100
|
|
|
716
|
if ( exists $parsed{$lc_k} && !exists $options_with_list_type{$lc_k} ) { |
256
|
2
|
|
|
|
|
26
|
warn "Multiple options were found for the same value '$lc_k'. The first occurrence will be used\n"; |
257
|
2
|
|
|
|
|
17
|
next; |
258
|
|
|
|
|
|
|
} |
259
|
263
|
100
|
|
|
|
5103
|
if ( $lc_k eq 'authmechanismproperties' ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
260
|
3
|
|
|
|
|
12
|
my $temp = _parse_doc( $k, $v ); |
261
|
3
|
50
|
|
|
|
13
|
if ( defined $temp ) { |
262
|
3
|
|
|
|
|
8
|
$parsed{$lc_k} = $temp; |
263
|
3
|
100
|
|
|
|
14
|
if ( exists $parsed{$lc_k}{CANONICALIZE_HOST_NAME} ) { |
264
|
2
|
|
|
|
|
13
|
my $temp = __str_to_bool( 'CANONICALIZE_HOST_NAME', $parsed{$lc_k}{CANONICALIZE_HOST_NAME} ); |
265
|
2
|
50
|
|
|
|
8
|
if ( defined $temp ) { |
266
|
2
|
|
|
|
|
7
|
$parsed{$lc_k}{CANONICALIZE_HOST_NAME} = $temp; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
elsif ( $lc_k eq 'compressors' ) { |
272
|
5
|
|
|
|
|
13
|
my @compressors = split /,/, $v, -1; |
273
|
5
|
|
|
|
|
15
|
my $valid_compressors = { |
274
|
|
|
|
|
|
|
snappy => 1, |
275
|
|
|
|
|
|
|
zlib => 1, |
276
|
|
|
|
|
|
|
zstd => 1 |
277
|
|
|
|
|
|
|
}; |
278
|
5
|
|
|
|
|
11
|
for my $compressor ( @compressors ) { |
279
|
|
|
|
|
|
|
warn("Unsupported compressor $compressor\n") |
280
|
6
|
50
|
|
|
|
16
|
unless $valid_compressors->{$compressor}; |
281
|
|
|
|
|
|
|
} |
282
|
5
|
|
|
|
|
22
|
$parsed{$lc_k} = [ @compressors ]; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
elsif ( $lc_k eq 'authsource' ) { |
285
|
14
|
|
|
|
|
47
|
$parsed{$lc_k} = $v; |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
elsif ( $lc_k eq 'readpreferencetags' ) { |
288
|
6
|
|
100
|
|
|
31
|
$parsed{$lc_k} ||= []; |
289
|
6
|
|
|
|
|
17
|
my $temp = _parse_doc( $k, $v ); |
290
|
6
|
100
|
|
|
|
16
|
if ( defined $temp ) { |
291
|
5
|
|
|
|
|
9
|
push @{$parsed{$lc_k}}, $temp; |
|
5
|
|
|
|
|
18
|
|
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
elsif ( $self->_valid_str_to_bool_options->{ $lc_k } ) { |
295
|
63
|
|
|
|
|
542
|
my $temp = __str_to_bool( $k, $v ); |
296
|
63
|
100
|
|
|
|
162
|
if ( defined $temp ) { |
297
|
56
|
|
|
|
|
167
|
$parsed{$lc_k} = $temp |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
elsif ( my $opt_validation = $self->_extra_options_validation->{ $lc_k } ) { |
301
|
64
|
100
|
|
|
|
1659
|
unless (ref $opt_validation eq 'CODE') { |
302
|
26
|
|
|
|
|
423
|
$opt_validation = $self->_extra_options_validation->{ $opt_validation }; |
303
|
|
|
|
|
|
|
} |
304
|
64
|
|
|
|
|
250
|
my $valid = eval { $opt_validation->($v) }; |
|
64
|
|
|
|
|
145
|
|
305
|
64
|
|
|
|
|
10741
|
my $err = "$@"; |
306
|
64
|
100
|
|
|
|
2339
|
if ( ! $valid ) { |
307
|
20
|
|
|
|
|
315
|
warn("Unsupported URI value '$k' = '$v': $err"); |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
else { |
310
|
44
|
|
|
|
|
174
|
$parsed{$lc_k} = $v; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
else { |
314
|
108
|
|
|
|
|
2810
|
$parsed{$lc_k} = $v; |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
} |
317
|
202
|
100
|
100
|
|
|
757
|
if ( |
|
|
|
100
|
|
|
|
|
318
|
|
|
|
|
|
|
exists $parsed{tlsinsecure} |
319
|
|
|
|
|
|
|
&& ( exists $parsed{tlsallowinvalidcertificates} |
320
|
|
|
|
|
|
|
|| exists $parsed{tlsallowinvalidhostnames} ) |
321
|
|
|
|
|
|
|
) |
322
|
|
|
|
|
|
|
{ |
323
|
8
|
|
|
|
|
61
|
MongoDB::Error->throw('tlsInsecure conflicts with other options'); |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
# If both exist, they must be identical. |
326
|
194
|
100
|
100
|
|
|
504
|
if ( exists( $parsed{tls} ) |
|
|
|
100
|
|
|
|
|
327
|
|
|
|
|
|
|
&& exists( $parsed{ssl} ) |
328
|
|
|
|
|
|
|
&& $parsed{tls} != $parsed{ssl} ) |
329
|
|
|
|
|
|
|
{ |
330
|
4
|
|
|
|
|
15
|
MongoDB::Error->throw('tls and ssl must have the same value'); |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
# If either exists, set them both. |
333
|
190
|
100
|
|
|
|
542
|
if ( exists $parsed{tls} ) { |
|
|
100
|
|
|
|
|
|
334
|
6
|
|
|
|
|
12
|
$parsed{ssl} = $parsed{tls}; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
elsif ( exists $parsed{ssl} ) { |
337
|
4
|
|
|
|
|
11
|
$parsed{tls} = $parsed{ssl}; |
338
|
|
|
|
|
|
|
} |
339
|
190
|
|
|
|
|
575
|
return \%parsed; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
sub _fetch_dns_seedlist { |
343
|
0
|
|
|
0
|
|
0
|
my ( $self, $host_name, $phase ) = @_; |
344
|
|
|
|
|
|
|
|
345
|
0
|
|
|
|
|
0
|
my @split_name = split( '\.', $host_name ); |
346
|
0
|
0
|
|
|
|
0
|
MongoDB::Error->throw("URI '$self' must contain domain name and hostname") |
347
|
|
|
|
|
|
|
unless scalar( @split_name ) > 2; |
348
|
|
|
|
|
|
|
|
349
|
0
|
|
|
|
|
0
|
require Net::DNS; |
350
|
|
|
|
|
|
|
|
351
|
0
|
|
|
|
|
0
|
my $res = Net::DNS::Resolver->new; |
352
|
0
|
|
|
|
|
0
|
my $srv_data = $res->query( sprintf( '_mongodb._tcp.%s', $host_name ), 'SRV' ); |
353
|
|
|
|
|
|
|
|
354
|
0
|
|
|
|
|
0
|
my @hosts; |
355
|
0
|
|
|
|
|
0
|
my $options = {}; |
356
|
0
|
|
|
|
|
0
|
my $domain_name = join( '.', @split_name[1..$#split_name] ); |
357
|
0
|
|
|
|
|
0
|
my $minimum_ttl; |
358
|
0
|
0
|
|
|
|
0
|
if ( $srv_data ) { |
359
|
0
|
|
|
|
|
0
|
SRV_RECORD: foreach my $rr ( $srv_data->answer ) { |
360
|
0
|
0
|
|
|
|
0
|
next unless $rr->type eq 'SRV'; |
361
|
0
|
|
|
|
|
0
|
my $target = $rr->target; |
362
|
|
|
|
|
|
|
# search for dot before domain name for a valid hostname - can have sub-subdomain |
363
|
0
|
0
|
|
|
|
0
|
unless ( $target =~ /\.\Q$domain_name\E$/ ) { |
364
|
0
|
|
|
|
|
0
|
my $err_msg = "URI '$self' SRV record returns FQDN '$target'" |
365
|
0
|
|
|
|
|
0
|
. " which does not match domain name '${$domain_name}'"; |
366
|
0
|
0
|
0
|
|
|
0
|
if ($phase && $phase eq 'init') { |
367
|
0
|
|
|
|
|
0
|
MongoDB::Error->throw($err_msg); |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
else { |
370
|
0
|
|
|
|
|
0
|
warn $err_msg; |
371
|
|
|
|
|
|
|
} |
372
|
0
|
|
|
|
|
0
|
next SRV_RECORD; |
373
|
|
|
|
|
|
|
} |
374
|
0
|
|
|
|
|
0
|
push @hosts, { |
375
|
|
|
|
|
|
|
target => $target, |
376
|
|
|
|
|
|
|
port => $rr->port, |
377
|
|
|
|
|
|
|
}; |
378
|
0
|
0
|
0
|
|
|
0
|
$minimum_ttl = $rr->ttl |
379
|
|
|
|
|
|
|
if not defined $minimum_ttl or $rr->ttl < $minimum_ttl; |
380
|
|
|
|
|
|
|
} |
381
|
0
|
|
|
|
|
0
|
my $txt_data = $res->query( $host_name, 'TXT' ); |
382
|
0
|
0
|
|
|
|
0
|
if ( defined $txt_data ) { |
383
|
0
|
|
|
|
|
0
|
my @txt_answers; |
384
|
0
|
|
|
|
|
0
|
foreach my $rr ( $txt_data->answer ) { |
385
|
0
|
0
|
|
|
|
0
|
next unless $rr->type eq 'TXT'; |
386
|
0
|
|
|
|
|
0
|
push @txt_answers, $rr; |
387
|
|
|
|
|
|
|
} |
388
|
0
|
0
|
|
|
|
0
|
if ( scalar( @txt_answers ) > 1 ) { |
|
|
0
|
|
|
|
|
|
389
|
0
|
|
|
|
|
0
|
MongoDB::Error->throw("URI '$self' returned more than one TXT result"); |
390
|
|
|
|
|
|
|
} elsif ( scalar( @txt_answers ) == 1 ) { |
391
|
0
|
|
|
|
|
0
|
my $txt_opt_string = join ( '', $txt_answers[0]->txtdata ); |
392
|
0
|
|
|
|
|
0
|
$options = $self->_parse_options( $self->valid_srv_options, { options => $txt_opt_string }, $txt_opt_string ); |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
} else { |
396
|
0
|
|
|
|
|
0
|
MongoDB::Error->throw("URI '$self' does not return any SRV results"); |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
|
399
|
0
|
0
|
|
|
|
0
|
unless (@hosts) { |
400
|
0
|
|
|
|
|
0
|
my $err_msg = "URI '$self' does not return any valid SRV results"; |
401
|
0
|
0
|
0
|
|
|
0
|
if ($phase && $phase eq 'init') { |
402
|
0
|
|
|
|
|
0
|
MongoDB::Error->throw($err_msg); |
403
|
|
|
|
|
|
|
} |
404
|
|
|
|
|
|
|
else { |
405
|
0
|
|
|
|
|
0
|
warn $err_msg; |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
0
|
0
|
0
|
|
|
0
|
$minimum_ttl = RESCAN_SRV_FREQUENCY_SEC |
|
|
|
0
|
|
|
|
|
410
|
|
|
|
|
|
|
if $minimum_ttl < RESCAN_SRV_FREQUENCY_SEC |
411
|
|
|
|
|
|
|
&& $phase && $phase ne 'init'; |
412
|
|
|
|
|
|
|
|
413
|
0
|
|
|
|
|
0
|
return ( \@hosts, $options, time + $minimum_ttl ); |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
sub _parse_srv_uri { |
417
|
5
|
|
|
5
|
|
3651
|
my ( $self, $uri, $phase ) = @_; |
418
|
|
|
|
|
|
|
|
419
|
5
|
|
|
|
|
28
|
my %result; |
420
|
|
|
|
|
|
|
|
421
|
5
|
|
|
|
|
229
|
$uri =~ m{^$uri_re$}; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
( |
424
|
|
|
|
|
|
|
$result{username}, $result{password}, $result{hostids}, |
425
|
|
|
|
|
|
|
$result{db_name}, $result{options} |
426
|
5
|
|
|
|
|
56
|
) = ( $1, $2, $3, $4, $5 ); |
427
|
|
|
|
|
|
|
|
428
|
5
|
|
|
|
|
18
|
$result{hostids} = lc _unescape_all( $result{hostids} ); |
429
|
|
|
|
|
|
|
|
430
|
5
|
50
|
33
|
|
|
37
|
if ( !defined $result{hostids} || !length $result{hostids} ) { |
431
|
0
|
|
|
|
|
0
|
MongoDB::Error->throw("URI '$self' cannot be empty if using an SRV connection string"); |
432
|
|
|
|
|
|
|
} |
433
|
|
|
|
|
|
|
|
434
|
5
|
100
|
|
|
|
21
|
if ( $result{hostids} =~ /,/ ) { |
435
|
1
|
|
|
|
|
15
|
MongoDB::Error->throw("URI '$self' cannot contain a comma or multiple host names if using an SRV connection string"); |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
|
438
|
4
|
100
|
|
|
|
18
|
if ( $result{hostids} =~ /:\d+$/ ) { |
439
|
1
|
|
|
|
|
5
|
MongoDB::Error->throw("URI '$self' cannot contain port number if using an SRV connection string"); |
440
|
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
3
|
100
|
|
|
|
10
|
if ( defined $result{options} ) { |
443
|
2
|
|
|
|
|
51
|
$result{options} = $self->_parse_options( $self->valid_options, \%result ); |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
|
446
|
3
|
|
|
|
|
11
|
my ( $hosts, $options, $expires ) = $self->_fetch_dns_seedlist( $result{hostids}, $phase ); |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
# Default to SSL on unless specified in conn string options |
449
|
|
|
|
|
|
|
$options = { |
450
|
|
|
|
|
|
|
ssl => 'true', |
451
|
|
|
|
|
|
|
%$options, |
452
|
3
|
100
|
|
|
|
43
|
%{ $result{options} || {} }, |
|
3
|
|
|
|
|
17
|
|
453
|
|
|
|
|
|
|
}; |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
# Reset str to bool options to string value, as _parse_options changes it to 0/1 if it exists during parsing |
456
|
|
|
|
|
|
|
# means we get the correct value when re-building the uri below. |
457
|
3
|
|
|
|
|
7
|
for my $stb_key ( keys %{ $self->_valid_str_to_bool_options } ) { |
|
3
|
|
|
|
|
60
|
|
458
|
|
|
|
|
|
|
# use exists just in case |
459
|
27
|
100
|
|
|
|
90
|
next unless exists $options->{ $stb_key }; |
460
|
5
|
50
|
33
|
|
|
18
|
$options->{ $stb_key } = ($options->{ $stb_key } || $options->{ $stb_key } eq 'true') ? 'true' : 'false'; |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
|
463
|
3
|
|
|
|
|
8
|
my $auth = ""; |
464
|
3
|
100
|
66
|
|
|
26
|
if ( defined $result{username} || defined $result{password} ) { |
465
|
1
|
|
100
|
|
|
3
|
$auth = join(":", map { $_ // "" } $result{username}, $result{password}); |
|
2
|
|
|
|
|
9
|
|
466
|
1
|
|
|
|
|
4
|
$auth .= "@"; |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
my $new_uri = sprintf( |
470
|
|
|
|
|
|
|
'mongodb://%s%s/%s%s%s', |
471
|
|
|
|
|
|
|
$auth, |
472
|
3
|
|
|
|
|
35
|
join( ',', map { sprintf( '%s:%s', $_->{target}, $_->{port} ) } @$hosts ), |
473
|
|
|
|
|
|
|
($result{db_name} // ""), |
474
|
|
|
|
|
|
|
scalar( keys %$options ) ? '?' : '', |
475
|
3
|
50
|
100
|
|
|
8
|
join( '&', map { sprintf( '%s=%s', $_, __uri_escape( $options->{$_} ) ) } keys %$options ), |
|
11
|
|
|
|
|
27
|
|
476
|
|
|
|
|
|
|
); |
477
|
|
|
|
|
|
|
|
478
|
3
|
|
|
|
|
20
|
return( $new_uri, $expires ); |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
sub BUILD { |
482
|
562
|
|
|
562
|
0
|
22633
|
my ($self) = @_; |
483
|
|
|
|
|
|
|
|
484
|
562
|
|
|
|
|
1622
|
$self->_initialize_from_uri; |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
# Options: |
488
|
|
|
|
|
|
|
# - fallback_ttl_sec: Fallback TTL in seconds in case of an error |
489
|
|
|
|
|
|
|
sub check_for_changes { |
490
|
0
|
|
|
0
|
0
|
0
|
my ($self, $options) = @_; |
491
|
|
|
|
|
|
|
|
492
|
0
|
0
|
0
|
|
|
0
|
if (defined $self->{expires} && $self->{expires} <= time) { |
493
|
0
|
|
|
|
|
0
|
my @current = sort @{ $self->{hostids} }; |
|
0
|
|
|
|
|
0
|
|
494
|
0
|
|
|
|
|
0
|
local $@; |
495
|
0
|
|
|
|
|
0
|
my $ok = eval { |
496
|
|
|
|
|
|
|
|
497
|
0
|
|
|
|
|
0
|
$self->_update_from_uri; |
498
|
0
|
|
|
|
|
0
|
1; |
499
|
|
|
|
|
|
|
}; |
500
|
0
|
0
|
|
|
|
0
|
if (!$ok) { |
501
|
0
|
|
|
|
|
0
|
warn "Error while fetching SRV records: $@"; |
502
|
0
|
|
|
|
|
0
|
$self->{expires} = $options->{fallback_ttl_sec}; |
503
|
|
|
|
|
|
|
}; |
504
|
0
|
0
|
|
|
|
0
|
return 0 |
505
|
|
|
|
|
|
|
unless $ok; |
506
|
0
|
|
|
|
|
0
|
my @new = sort @{ $self->{hostids} }; |
|
0
|
|
|
|
|
0
|
|
507
|
0
|
0
|
|
|
|
0
|
return 1 |
508
|
|
|
|
|
|
|
unless @current == @new; |
509
|
0
|
|
|
|
|
0
|
for my $index (0 .. $#current) { |
510
|
0
|
0
|
|
|
|
0
|
return 1 |
511
|
|
|
|
|
|
|
unless $new[$index] eq $current[$index]; |
512
|
|
|
|
|
|
|
} |
513
|
0
|
|
|
|
|
0
|
return 0; |
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
|
516
|
0
|
|
|
|
|
0
|
return 0; |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
sub _prepare_dns_hosts { |
520
|
546
|
|
|
546
|
|
1184
|
my ($self, $hostids) = @_; |
521
|
|
|
|
|
|
|
|
522
|
546
|
100
|
66
|
|
|
2904
|
if ( !defined $hostids || !length $hostids ) { |
523
|
5
|
|
|
|
|
23
|
MongoDB::Error->throw("URI '$self' could not be parsed (missing host list)"); |
524
|
|
|
|
|
|
|
} |
525
|
541
|
|
|
|
|
1959
|
$hostids = [ map { lc _unescape_all($_) } split ',', $hostids ]; |
|
599
|
|
|
|
|
1392
|
|
526
|
541
|
|
|
|
|
1509
|
for my $hostid (@$hostids) { |
527
|
584
|
100
|
66
|
|
|
2260
|
MongoDB::Error->throw( |
528
|
|
|
|
|
|
|
"URI '$self' could not be parsed (Unix domain sockets are not supported)") |
529
|
|
|
|
|
|
|
if $hostid =~ /\// && $hostid =~ /\.sock/; |
530
|
550
|
100
|
|
|
|
1804
|
MongoDB::Error->throw( |
531
|
|
|
|
|
|
|
"URI '$self' could not be parsed (IP literals are not supported)") |
532
|
|
|
|
|
|
|
if substr( $hostid, 0, 1 ) eq '['; |
533
|
535
|
|
|
|
|
1665
|
my ( $host, $port ) = split ":", $hostid, 2; |
534
|
535
|
100
|
|
|
|
1378
|
MongoDB::Error->throw("host list '@{ $hostids }' contains empty host") |
|
3
|
|
|
|
|
19
|
|
535
|
|
|
|
|
|
|
unless length $host; |
536
|
532
|
100
|
|
|
|
1434
|
if ( defined $port ) { |
537
|
139
|
100
|
|
|
|
763
|
MongoDB::Error->throw("URI '$self' could not be parsed (invalid port '$port')") |
538
|
|
|
|
|
|
|
unless $port =~ /^\d+$/; |
539
|
130
|
100
|
100
|
|
|
1026
|
MongoDB::Error->throw( |
540
|
|
|
|
|
|
|
"URI '$self' could not be parsed (invalid port '$port' (must be in range [1,65535])") |
541
|
|
|
|
|
|
|
unless $port >= 1 && $port <= 65535; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
} |
544
|
475
|
100
|
|
|
|
1063
|
$hostids = [ map { /:/ ? $_ : $_.":27017" } @$hostids ]; |
|
511
|
|
|
|
|
2429
|
|
545
|
475
|
|
|
|
|
1426
|
return $hostids; |
546
|
|
|
|
|
|
|
} |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
sub _update_from_uri { |
549
|
0
|
|
|
0
|
|
0
|
my ($self) = @_; |
550
|
|
|
|
|
|
|
|
551
|
0
|
|
|
|
|
0
|
my $uri = $self->uri; |
552
|
0
|
|
|
|
|
0
|
my %result; |
553
|
|
|
|
|
|
|
|
554
|
0
|
|
|
|
|
0
|
($uri, my $expires) = $self->_parse_srv_uri( $uri ); |
555
|
0
|
|
|
|
|
0
|
$self->{expires} = $expires; |
556
|
|
|
|
|
|
|
|
557
|
0
|
0
|
|
|
|
0
|
if ( $uri !~ m{^$uri_re$} ) { |
558
|
0
|
|
|
|
|
0
|
MongoDB::Error->throw("URI '$self' could not be parsed"); |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
0
|
|
|
|
|
0
|
my $hostids = $3; |
562
|
0
|
|
|
|
|
0
|
$hostids = $self->_prepare_dns_hosts($hostids); |
563
|
|
|
|
|
|
|
|
564
|
0
|
|
|
|
|
0
|
$self->{hostids} = $hostids; |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
sub _initialize_from_uri { |
568
|
562
|
|
|
562
|
|
1354
|
my ($self) = @_; |
569
|
|
|
|
|
|
|
|
570
|
562
|
|
|
|
|
1502
|
my $uri = $self->uri; |
571
|
562
|
|
|
|
|
1118
|
my %result; |
572
|
|
|
|
|
|
|
|
573
|
562
|
100
|
|
|
|
1905
|
if ( $uri =~ m{^mongodb\+srv://} ) { |
574
|
4
|
|
|
|
|
27
|
($uri, my $expires) = $self->_parse_srv_uri( $uri, 'init' ); |
575
|
2
|
|
|
|
|
6
|
$result{expires} = $expires; |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
# we throw Error instead of UsageError for errors, to avoid stacktrace revealing credentials |
579
|
560
|
100
|
|
|
|
11245
|
if ( $uri !~ m{^$uri_re$} ) { |
580
|
5
|
|
|
|
|
22
|
MongoDB::Error->throw("URI '$self' could not be parsed"); |
581
|
|
|
|
|
|
|
} |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
( |
584
|
|
|
|
|
|
|
$result{username}, $result{password}, $result{hostids}, |
585
|
|
|
|
|
|
|
$result{db_name}, $result{options} |
586
|
555
|
|
|
|
|
4231
|
) = ( $1, $2, $3, $4, $5 ); |
587
|
|
|
|
|
|
|
|
588
|
555
|
100
|
|
|
|
1820
|
if ( defined $result{username} ) { |
589
|
|
|
|
|
|
|
MongoDB::Error->throw( |
590
|
|
|
|
|
|
|
"URI '$self' could not be parsed (username must be URL encoded)" |
591
|
72
|
100
|
|
|
|
203
|
) if __userinfo_invalid_chars($result{username}); |
592
|
66
|
|
|
|
|
187
|
$result{username} = _unescape_all( $result{username} ); |
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
|
595
|
549
|
100
|
|
|
|
1336
|
if ( defined $result{password} ) { |
596
|
|
|
|
|
|
|
MongoDB::Error->throw( |
597
|
|
|
|
|
|
|
"URI '$self' could not be parsed (password must be URL encoded)" |
598
|
49
|
100
|
|
|
|
112
|
) if __userinfo_invalid_chars($result{password}); |
599
|
46
|
|
|
|
|
123
|
$result{password} = _unescape_all( $result{password} ); |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
|
602
|
546
|
|
|
|
|
1522
|
$result{hostids} = $self->_prepare_dns_hosts($result{hostids}); |
603
|
|
|
|
|
|
|
|
604
|
475
|
100
|
|
|
|
1261
|
if ( defined $result{db_name} ) { |
605
|
|
|
|
|
|
|
MongoDB::Error->throw( |
606
|
|
|
|
|
|
|
"URI '$self' could not be parsed (database name must be URL encoded, found unescaped '/'" |
607
|
226
|
100
|
|
|
|
622
|
) if $result{db_name} =~ /\//; |
608
|
225
|
|
|
|
|
472
|
$result{db_name} = _unescape_all( $result{db_name} ); |
609
|
|
|
|
|
|
|
} |
610
|
|
|
|
|
|
|
|
611
|
474
|
100
|
|
|
|
1198
|
if ( defined $result{options} ) { |
612
|
206
|
|
|
|
|
4462
|
$result{options} = $self->_parse_options( $self->valid_options, \%result ); |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
|
615
|
456
|
|
|
|
|
1067
|
for my $attr (qw/username password db_name options hostids expires/) { |
616
|
2736
|
|
|
|
|
27014
|
my $setter = "_set_$attr"; |
617
|
2736
|
100
|
|
|
|
22889
|
$self->$setter( $result{$attr} ) if defined $result{$attr}; |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
456
|
|
|
|
|
7302
|
return; |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
sub __str_to_bool { |
624
|
65
|
|
|
65
|
|
146
|
my ($k, $str) = @_; |
625
|
65
|
50
|
|
|
|
151
|
MongoDB::UsageError->throw("cannot convert undef to bool for key '$k'") |
626
|
|
|
|
|
|
|
unless defined $str; |
627
|
65
|
100
|
|
|
|
224
|
my $ret = $str eq "true" ? 1 : $str eq "false" ? 0 : undef; |
|
|
100
|
|
|
|
|
|
628
|
65
|
100
|
|
|
|
217
|
warn("expected boolean string 'true' or 'false' for key '$k' but instead received '$str'. Ignoring '$k'.\n") |
629
|
|
|
|
|
|
|
unless defined $ret; |
630
|
65
|
|
|
|
|
176
|
return $ret; |
631
|
|
|
|
|
|
|
} |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
# uri_escape borrowed from HTTP::Tiny 0.070 |
634
|
|
|
|
|
|
|
my %escapes = map { chr($_) => sprintf("%%%02X", $_) } 0..255; |
635
|
|
|
|
|
|
|
$escapes{' '}="+"; |
636
|
|
|
|
|
|
|
my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/; |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
sub __uri_escape { |
639
|
11
|
|
|
11
|
|
18
|
my ($str) = @_; |
640
|
11
|
50
|
|
|
|
29
|
if ( $] ge '5.008' ) { |
641
|
11
|
|
|
|
|
26
|
utf8::encode($str); |
642
|
|
|
|
|
|
|
} |
643
|
|
|
|
|
|
|
else { |
644
|
|
|
|
|
|
|
$str = pack("U*", unpack("C*", $str)) # UTF-8 encode a byte string |
645
|
64
|
0
|
|
64
|
|
686
|
if ( length $str == do { use bytes; length $str } ); |
|
64
|
|
|
|
|
175
|
|
|
64
|
|
|
|
|
604
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
646
|
0
|
|
|
|
|
0
|
$str = pack("C*", unpack("C*", $str)); # clear UTF-8 flag |
647
|
|
|
|
|
|
|
} |
648
|
11
|
|
|
|
|
51
|
$str =~ s/($unsafe_char)/$escapes{$1}/ge; |
|
0
|
|
|
|
|
0
|
|
649
|
11
|
|
|
|
|
46
|
return $str; |
650
|
|
|
|
|
|
|
} |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
# Rules for valid userinfo from RFC 3986 Section 3.2.1. |
653
|
|
|
|
|
|
|
my $unreserved = q[a-z0-9._~-]; # use this class last so regex ends in '-' |
654
|
|
|
|
|
|
|
my $subdelimit = q[!$&'()*+,;=]; |
655
|
|
|
|
|
|
|
my $allowed = "%$subdelimit$unreserved"; |
656
|
|
|
|
|
|
|
my $not_allowed_re = qr/[^$allowed]/i; |
657
|
|
|
|
|
|
|
my $not_pct_enc_re = qr/%(?![0-9a-f]{2})/i; |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
sub __userinfo_invalid_chars { |
660
|
121
|
|
|
121
|
|
218
|
my ($str) = @_; |
661
|
121
|
|
100
|
|
|
1004
|
return $str =~ $not_pct_enc_re || $str =~ $not_allowed_re; |
662
|
|
|
|
|
|
|
} |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
# redact user credentials when stringifying |
665
|
|
|
|
|
|
|
use overload |
666
|
|
|
|
|
|
|
'""' => sub { |
667
|
120
|
|
|
120
|
|
13762
|
(my $s = $_[0]->uri) =~ s{^([^:]+)://[^/]+\@}{$1://[**REDACTED**]\@}; |
668
|
120
|
|
|
|
|
850
|
return $s |
669
|
|
|
|
|
|
|
}, |
670
|
64
|
|
|
64
|
|
19582
|
'fallback' => 1; |
|
64
|
|
|
|
|
204
|
|
|
64
|
|
|
|
|
885
|
|
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
1; |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
# vim: ts=4 sts=4 sw=4 et: |