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