File Coverage

blib/lib/BSON/PP.pm
Criterion Covered Total %
statement 335 380 88.1
branch 242 284 85.2
condition 109 132 82.5
subroutine 30 32 93.7
pod n/a
total 716 828 86.4


line stmt bran cond sub pod time code
1 71     71   33844 use 5.010001;
  71         261  
2 71     71   354 use strict;
  71         139  
  71         1412  
3 71     71   326 use warnings;
  71         332  
  71         2242  
4 71     71   401 no warnings 'recursion';
  71         147  
  71         3372  
5              
6             package BSON::PP;
7             # ABSTRACT: Pure Perl BSON implementation
8              
9 71     71   430 use version;
  71         154  
  71         499  
10             our $VERSION = 'v1.12.1';
11              
12 71     71   6265 use B;
  71         153  
  71         3624  
13 71     71   407 use Carp;
  71         151  
  71         3873  
14 71     71   494 use Config;
  71         157  
  71         3270  
15 71     71   422 use Scalar::Util qw/blessed looks_like_number refaddr reftype/;
  71         127  
  71         4339  
16 71     71   428 use List::Util qw/first/;
  71         138  
  71         7546  
17 71     71   798 use Tie::IxHash;
  71         306086  
  71         2064  
18              
19 71     71   800 use BSON::Types ();
  71         258  
  71         1879  
20 71     71   444 use boolean;
  71         160  
  71         408  
21 71     71   5158 use mro;
  71         146  
  71         568  
22              
23 71     71   1951 use re 'regexp_pattern';
  71         148  
  71         11770  
24              
25             use constant {
26             HAS_INT64 => $Config{use64bitint},
27 71     71   532 };
  71         179  
  71         5870  
28              
29 71     71   428 use if !HAS_INT64, "Math::BigInt";
  71         145  
  71         421  
30              
31             # Max integer sizes
32             my $max_int32 = 2147483647;
33             my $min_int32 = -2147483648;
34             my $max_int64 =
35             HAS_INT64 ? 9223372036854775807 : Math::BigInt->new("9223372036854775807");
36             my $min_int64 =
37             HAS_INT64 ? -9223372036854775808 : Math::BigInt->new("-9223372036854775808");
38              
39             #<<<
40             my $int_re = qr/^(?:(?:[+-]?)(?:[0123456789]+))$/;
41             my $doub_re = qr/^(?:(?i)(?:NaN|-?Inf(?:inity)?)|(?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[0123456789]+))|))$/;
42             #>>>
43              
44             my $bools_re = qr/::(?:Boolean|_Bool|Bool)\z/;
45              
46             use constant {
47              
48 71         375581 BSON_TYPE_NAME => "CZ*",
49             BSON_DOUBLE => "d<",
50             BSON_STRING => "V/Z*",
51             BSON_BOOLEAN => "C",
52             BSON_REGEX => "Z*Z*",
53             BSON_JSCODE => "",
54             BSON_INT32 => "l<",
55             BSON_UINT32 => "L<",
56             BSON_INT64 => "q<",
57             BSON_8BYTES => "a8",
58             BSON_16BYTES => "a16",
59             BSON_TIMESTAMP => "L
60             BSON_CODE_W_SCOPE => "l<",
61             BSON_REMAINING => 'a*',
62             BSON_SKIP_4_BYTES => 'x4',
63             BSON_OBJECTID => 'a12',
64             BSON_BINARY_TYPE => 'C',
65             BSON_CSTRING => 'Z*',
66             BSON_MAX_DEPTH => 100,
67 71     71   20503 };
  71         152  
68              
69             sub _printable {
70 1     1   2 my $value = shift;
71 1         8 $value =~ s/([^[:print:]])/sprintf("\\x%02x",ord($1))/ge;
  1         9  
72 1         111 return $value;
73             }
74              
75             sub _split_re {
76 5     5   4493 my $value = shift;
77 5 50       44 if ( $] ge 5.010 ) {
78 5         26 return re::regexp_pattern($value);
79             }
80             else {
81 0         0 $value =~ s/^\(\?\^?//;
82 0         0 $value =~ s/\)$//;
83 0         0 my ( $opt, $re ) = split( /:/, $value, 2 );
84 0         0 $opt =~ s/\-\w+$//;
85 0         0 return ( $re, $opt );
86             }
87             }
88              
89             sub _ixhash_iterator {
90 21     21   824 my $ixhash = shift;
91 21         35 my $started = 0;
92             return sub {
93 67 100   67   168 my $k = $started ? $ixhash->NEXTKEY : do { $started++; $ixhash->FIRSTKEY };
  21         32  
  21         56  
94 67 100       531 return unless defined $k;
95 47         109 return ($k, $ixhash->FETCH($k));
96             }
97 21         94 }
98              
99             # relying on Perl's each() is prone to action-at-a-distance effects we
100             # want to avoid, so we construct our own iterator for hashes
101             sub _hashlike_iterator {
102 29886     29886   45263 my $hashlike = shift;
103 29886         110115 my @keys = keys %$hashlike;
104             @keys = sort @keys
105 29886 50       258333 if $ENV{BSON_TEST_SORT_HASH};
106             return sub {
107 174495     174495   364596 my $k = shift @keys;
108 174495 100       347462 return unless defined $k;
109 144818         472103 return ($k, $hashlike->{$k});
110             }
111 29886         151547 }
112              
113             # XXX could be optimized down to only one substr to trim/pad
114             sub _bigint_to_int64 {
115 10     10   23 my $bigint = shift;
116 10         24 my $neg = $bigint < 0;
117 10 100       1639 if ( $neg ) {
    50          
118 1 50       3 if ( $bigint < $min_int64 ) {
119 0         0 return "\x80\x00\x00\x00\x00\x00\x00\x00";
120             }
121 1         129 $bigint = abs($bigint) - ($max_int64 + 1);
122             }
123             elsif ( $bigint > $max_int64 ) {
124 0         0 return "\x7f\xff\xff\xff\xff\xff\xff\xff";
125             }
126              
127 10         1282 my $as_hex = $bigint->as_hex; # big-endian hex
128 10         998 $as_hex =~ s{-?0x}{};
129 10         21 my $len = length($as_hex);
130 10 100       44 substr( $as_hex, 0, 0, "0" x ( 16 - $len ) ) if $len < 16; # pad to quad length
131 10         31 my $pack = pack( "H*", $as_hex );
132 10 100       23 $pack |= "\x80\x00\x00\x00\x00\x00\x00\x00" if $neg;
133 10         55 return scalar reverse $pack;
134             }
135              
136             sub _int64_to_bigint {
137 0     0   0 my $bytes = reverse(shift);
138 0 0       0 return Math::BigInt->new() if $bytes eq "\x00\x00\x00\x00\x00\x00\x00\x00";
139 0 0       0 if ( unpack("c", $bytes) < 0 ) {
140 0 0       0 if ( $bytes eq "\x80\x00\x00\x00\x00\x00\x00\x00" ) {
141 0         0 return -1 * Math::BigInt->new( "0x" . unpack("H*",$bytes) );
142             }
143             else {
144 0         0 return -1 * Math::BigInt->new( "0x" . unpack( "H*", ~$bytes ) ) - 1;
145             }
146             }
147             else {
148 0         0 return Math::BigInt->new( "0x" . unpack( "H*", $bytes ) );
149             }
150             }
151              
152             sub _pack_int64 {
153 17676     17676   27558 my $value = shift;
154 17676         23670 my $type = ref($value);
155              
156             # if no type, then on 64-big perl we can pack with 'q'; otherwise
157             # we need to convert scalars to Math::BigInt and pack them that way.
158 17676 100       28983 if ( ! $type ) {
159 17666         69644 return pack(BSON_INT64,$value ) if HAS_INT64;
160 0         0 $value = Math::BigInt->new($value);
161 0         0 $type = 'Math::BigInt';
162             }
163              
164 10 50       21 if ( $type eq 'Math::BigInt' ) {
    0          
165 10         22 return _bigint_to_int64($value);
166             }
167             elsif ( $type eq 'Math::Int64' ) {
168 0         0 return Math::Int64::int64_to_native($value);
169             }
170             else {
171 0         0 croak "Don't know how to encode $type '$value' as an Int64.";
172             }
173             }
174              
175             sub _reftype_check {
176 1     1   2 my $doc = shift;
177 1         3 my $type = ref($doc);
178 1         5 my $reftype = reftype($doc);
179 1 50       13 die "Can't encode non-container of type '$type'" unless $reftype eq 'HASH';
180 0         0 return;
181             }
182              
183             sub _encode_bson {
184 30037     30037   55166 my ($doc, $opt) = @_;
185              
186 30037         57122 my $refaddr = refaddr($doc);
187 30037 100       107418 die "circular reference detected" if $opt->{_circular}{$refaddr}++;
188              
189 30033 100       63046 $opt->{_depth} = 0 unless defined $opt->{_depth};
190 30033         38008 $opt->{_depth}++;
191 30033 100       56300 if ($opt->{_depth} > BSON_MAX_DEPTH) {
192 2         1406 croak "Exceeded max object depth of ". BSON_MAX_DEPTH;
193             }
194              
195 30031         45788 my $doc_type = ref($doc);
196              
197 30031 100 66     93731 if ( $doc_type eq 'BSON::Raw' || $doc_type eq 'MongoDB::BSON::_EncodedDoc' ) {
198 103         183 delete $opt->{_circular}{$refaddr};
199 103         126 $opt->{_depth}--;
200 103         430 return $doc->bson;
201             }
202              
203 29928 100       49236 if ( $doc_type eq 'MongoDB::BSON::Raw' ) {
204 3         5 delete $opt->{_circular}{$refaddr};
205 3         4 $opt->{_depth}--;
206 3         11 return $$doc;
207             }
208              
209             my $iter =
210             $doc_type eq 'HASH' ? undef
211             : $doc_type eq 'BSON::Doc' ? $doc->_iterator
212             : $doc_type eq 'Tie::IxHash' ? _ixhash_iterator($doc)
213             : $doc_type eq 'BSON::DBRef' ? _ixhash_iterator( $doc->_ordered )
214             : $doc_type eq 'MongoDB::DBRef' ? _ixhash_iterator( $doc->_ordered )
215 29925 50       52093 : do { _reftype_check($doc); undef };
  1 100       5  
  0 100       0  
    100          
    100          
216              
217 29924   66     78820 $iter //= _hashlike_iterator($doc);
218              
219 29924 100       64621 my $op_char = defined($opt->{op_char}) ? $opt->{op_char} : '';
220             my $invalid =
221 29924 100       53907 length( $opt->{invalid_chars} ) ? qr/[\Q$opt->{invalid_chars}\E]/ : undef;
222              
223             # Set up first key bookkeeping
224 29924         45603 my $first_key_pending = !! defined($opt->{first_key});
225 29924         35099 my $first_key;
226 29924         40783 my $bson = '';
227              
228 29924         41322 my ($key, $value);
229 29924   100     65521 while ( $first_key_pending or ( $key, $value ) = $iter->() ) {
230 144890 100 100     498968 next if defined $first_key && $key eq $first_key;
231              
232 144889 100       220073 if ( $first_key_pending ) {
233 2         5 $first_key = $key = delete $opt->{first_key};
234 2         4 $value = delete $opt->{first_value};
235 2         4 undef $first_key_pending;
236             }
237              
238 144889 50       213302 last unless defined $key;
239              
240 144889 100       260672 croak "Key '" . _printable($key) . "' contains null character"
241             unless -1 == index($key, "\0");
242              
243 144888 100 100     249951 substr( $key, 0, 1 ) = '$'
244             if length($op_char) && substr( $key, 0, 1 ) eq $op_char;
245              
246 144888 100 66     235128 if ( $invalid && $key =~ $invalid ) {
247             croak(
248             sprintf(
249             "key '%s' has invalid character(s) '%s'",
250             $key, $opt->{invalid_chars}
251             )
252 2         195 );
253             }
254              
255 144886         197440 my $utf8_key = $key;
256 144886         314246 utf8::encode($utf8_key);
257 144886         230175 my $type = ref $value;
258              
259             # If the type is a subtype of BSON::*, use that instead
260 144886 100       310601 if ( blessed $value ) {
261 90486 100       288345 if ($type !~ /\ABSON::\w+\z/) {
262 39     57   132 my $parent = first { /\ABSON::\w+\z/ } reverse @{mro::get_linear_isa($type)};
  57         129  
  39         242  
263 39 50       166 $type = $parent if defined $parent;
264             }
265             }
266              
267             # Null
268 144886 100       293325 if ( !defined $value ) {
    100          
269 8890         26546 $bson .= pack( BSON_TYPE_NAME, 0x0A, $utf8_key );
270             }
271              
272             # REFERENCES/OBJECTS
273             elsif ( length $type ) {
274              
275             # Array
276 109254 100 100     1746839 if ( $type eq 'ARRAY' || $type eq 'BSON::Array' ) {
    100 100        
    100 100        
    100 66        
    100 100        
    100 100        
    50 100        
    100 66        
    50 66        
    50 100        
    50 100        
    50 100        
    100 100        
    50 100        
    100 66        
    100 66        
    100 100        
    100 100        
    100 100        
    100 66        
    100          
    100          
    100          
    100          
    100          
277 8924         11496 my $i = 0;
278 8924         32718 tie( my %h, 'Tie::IxHash' );
279 8924         152897 %h = map { $i++ => $_ } @$value;
  28656         53423  
280 8924         519043 $bson .= pack( BSON_TYPE_NAME, 0x04, $utf8_key ) . _encode_bson( \%h, $opt );
281             }
282              
283             # special-cased deprecated DBPointer
284             elsif ($type eq 'BSON::DBPointer') {
285 4         8 my %data;
286 4         14 tie %data, 'Tie::IxHash';
287 4         86 $data{'$ref'} = $value->{'ref'};
288 4         65 $data{'$id'} = $value->{id};
289 4         64 $bson .= pack( BSON_TYPE_NAME, 0x03, $utf8_key )
290             . _encode_bson(\%data, $opt);
291             }
292              
293             # Document
294             elsif ($type eq 'HASH'
295             || $type eq 'BSON::Doc'
296             || $type eq 'BSON::Raw'
297             || $type eq 'MongoDB::BSON::_EncodedDoc'
298             || $type eq 'Tie::IxHash'
299             || $type eq 'MongoDB::BSON::Raw'
300             || $type eq 'BSON::DBRef'
301             || $type eq 'MongoDB::DBRef')
302             {
303 9968         28240 $bson .= pack( BSON_TYPE_NAME, 0x03, $utf8_key ) . _encode_bson($value, $opt);
304             }
305              
306             # Regex
307             elsif ( $type eq 'Regexp' ) {
308 4         16 my ( $re, $flags ) = _split_re($value);
309 4         51 $bson .= pack( BSON_TYPE_NAME.BSON_REGEX, 0x0B, $utf8_key, $re, join( "", sort grep /^(i|m|x|l|s|u)$/, split( //, $flags ) ));
310             }
311             elsif ( $type eq 'BSON::Regex' || $type eq 'MongoDB::BSON::Regexp' ) {
312 8881         12593 my ( $re, $flags ) = @{$value}{qw/pattern flags/};
  8881         26930  
313 8881         35448 $bson .= pack( BSON_TYPE_NAME.BSON_REGEX, 0x0B, $utf8_key, $re, $flags) ;
314             }
315              
316             # ObjectId
317             elsif ( $type eq 'BSON::OID' || $type eq 'BSON::ObjectId' ) {
318 8912         43842 $bson .= pack( BSON_TYPE_NAME.BSON_OBJECTID, 0x07, $utf8_key, $value->oid );
319             }
320             elsif ( $type eq 'MongoDB::OID' ) {
321 0         0 $bson .= pack( BSON_TYPE_NAME."H*", 0x07, $utf8_key, $value->value );
322             }
323              
324             # Datetime
325             elsif ( $type eq 'BSON::Time' ) {
326 8841         32772 $bson .= pack( BSON_TYPE_NAME, 0x09, $utf8_key ) . _pack_int64( $value->value );
327             }
328             elsif ( $type eq 'Time::Moment' ) {
329 0         0 $bson .= pack( BSON_TYPE_NAME, 0x09, $utf8_key ) . _pack_int64( int( $value->epoch * 1000 + $value->millisecond ) );
330             }
331             elsif ( $type eq 'DateTime' ) {
332 0 0       0 if ( $value->time_zone->name eq 'floating' ) {
333 0         0 warn("saving floating timezone as UTC");
334             }
335 0         0 $bson .= pack( BSON_TYPE_NAME, 0x09, $utf8_key ) . _pack_int64( int( $value->hires_epoch * 1000 ) );
336             }
337             elsif ( $type eq 'DateTime::Tiny' ) {
338 0         0 require Time::Local;
339 0         0 my $epoch = Time::Local::timegm(
340             $value->second, $value->minute, $value->hour,
341             $value->day, $value->month - 1, $value->year,
342             );
343 0         0 $bson .= pack( BSON_TYPE_NAME, 0x09, $utf8_key ) . _pack_int64( $epoch * 1000 );
344             }
345             elsif ( $type eq 'Mango::BSON::Time' ) {
346 0         0 $bson .= pack( BSON_TYPE_NAME, 0x09, $utf8_key ) . _pack_int64( $value->{time} );
347             }
348              
349             # Timestamp
350             elsif ( $type eq 'BSON::Timestamp' ) {
351 8872         48765 $bson .= pack( BSON_TYPE_NAME.BSON_TIMESTAMP, 0x11, $utf8_key, $value->increment, $value->seconds );
352             }
353             elsif ( $type eq 'MongoDB::Timestamp' ){
354 0         0 $bson .= pack( BSON_TYPE_NAME.BSON_TIMESTAMP, 0x11, $utf8_key, $value->inc, $value->sec );
355             }
356              
357             # MinKey
358             elsif ( $type eq 'BSON::MinKey' || $type eq 'MongoDB::MinKey' ) {
359 8789         31808 $bson .= pack( BSON_TYPE_NAME, 0xFF, $utf8_key );
360             }
361              
362             # MaxKey
363             elsif ( $type eq 'BSON::MaxKey' || $type eq 'MongoDB::MaxKey' ) {
364 8715         31078 $bson .= pack( BSON_TYPE_NAME, 0x7F, $utf8_key );
365             }
366              
367             # Binary (XXX need to add string ref support)
368             elsif ($type eq 'SCALAR'
369             || $type eq 'BSON::Bytes'
370             || $type eq 'BSON::Binary'
371             || $type eq 'MongoDB::BSON::Binary' )
372             {
373             my $data =
374             $type eq 'SCALAR' ? $$value
375             : $type eq 'BSON::Bytes' ? $value->data
376             : $type eq 'MongoDB::BSON::Binary' ? $value->data
377 8960 50       27589 : pack( "C*", @{ $value->data } );
  4 100       12  
    100          
378 8960 100       19479 my $subtype = $type eq 'SCALAR' ? 0 : $value->subtype;
379 8960         11092 my $len = length($data);
380 8960 100       13410 if ( $subtype == 2 ) {
381 2         14 $bson .=
382             pack( BSON_TYPE_NAME . BSON_INT32 . BSON_BINARY_TYPE . BSON_INT32 . BSON_REMAINING,
383             0x05, $utf8_key, $len + 4, $subtype, $len, $data );
384             }
385             else {
386 8958         38980 $bson .= pack( BSON_TYPE_NAME . BSON_INT32 . BSON_BINARY_TYPE . BSON_REMAINING,
387             0x05, $utf8_key, $len, $subtype, $data );
388             }
389             }
390              
391             # Code
392             elsif ( $type eq 'BSON::Code' || $type eq 'MongoDB::Code' ) {
393 8985         25566 my $code = $value->code;
394 8985         34475 utf8::encode($code);
395 8985         32097 $code = pack(BSON_STRING,$code);
396 8985 100       24772 if ( ref( $value->scope ) eq 'HASH' ) {
397 8659         18778 my $scope = _encode_bson( $value->scope, $opt );
398 8659         43786 $bson .=
399             pack( BSON_TYPE_NAME.BSON_CODE_W_SCOPE, 0x0F, $utf8_key, (4 + length($scope) + length($code)) ) . $code . $scope;
400             }
401             else {
402 326         1415 $bson .= pack( BSON_TYPE_NAME, 0x0D, $utf8_key) . $code;
403             }
404             }
405              
406             # Boolean
407             elsif ( $type eq 'boolean' || $type =~ $bools_re ) {
408 8976 100       55913 $bson .= pack( BSON_TYPE_NAME.BSON_BOOLEAN, 0x08, $utf8_key, ( $value ? 1 : 0 ) );
409             }
410              
411             # String (explicit)
412             elsif ( $type eq 'BSON::String' || $type eq 'BSON::Symbol') {
413 8817         25487 $value = $value->value;
414 8817         27555 utf8::encode($value);
415 8817         40117 $bson .= pack( BSON_TYPE_NAME.BSON_STRING, 0x02, $utf8_key, $value );
416             }
417             elsif ( $type eq 'MongoDB::BSON::String' ) {
418 2         5 $value = $$value;
419 2         6 utf8::encode($value);
420 2         13 $bson .= pack( BSON_TYPE_NAME.BSON_STRING, 0x02, $utf8_key, $value );
421             }
422              
423             # Int64 (XXX and eventually BigInt)
424             elsif ( $type eq 'BSON::Int64' || $type eq 'Math::BigInt' || $type eq 'Math::Int64' )
425             {
426 24 50 33     364 if ( $value > $max_int64 || $value < $min_int64 ) {
427 0         0 croak("BSON can only handle 8-byte integers. Key '$key' is '$value'");
428             }
429              
430             # unwrap BSON::Int64; it could be Math::BigInt, etc.
431 24 100       2306 if ( $type eq 'BSON::Int64' ) {
432 16         48 $value = $value->value;
433             }
434              
435 24         102 $bson .= pack( BSON_TYPE_NAME, 0x12, $utf8_key ) . _pack_int64($value);
436             }
437              
438             elsif ( $type eq 'BSON::Int32' ) {
439 35         237 $bson .= pack( BSON_TYPE_NAME . BSON_INT32, 0x10, $utf8_key, $value->value );
440             }
441              
442             # Double (explicit)
443             elsif ( $type eq 'BSON::Double' ) {
444 32         633 $bson .= pack( BSON_TYPE_NAME.BSON_DOUBLE, 0x01, $utf8_key, $value/1.0 );
445             }
446              
447             # Decimal128
448             elsif ( $type eq 'BSON::Decimal128' ) {
449 1512         31319 $bson .= pack( BSON_TYPE_NAME.BSON_16BYTES, 0x13, $utf8_key, $value->bytes );
450             }
451              
452             # Unsupported type
453             else {
454 1         199 croak("For key '$key', can't encode value of type '$type'");
455             }
456             }
457              
458             # SCALAR
459             else {
460             # If a numeric value exists based on internal flags, use it;
461             # otherwise, if prefer_numeric is true and it looks like a
462             # number, then coerce to a number of the right type;
463             # otherwise, leave it as a string
464              
465 26742         89930 my $flags = B::svref_2object(\$value)->FLAGS;
466              
467 26742 100 100     65294 if ( $flags & B::SVf_NOK() ) {
    100          
    100          
468 8943         32460 $bson .= pack( BSON_TYPE_NAME.BSON_DOUBLE, 0x01, $utf8_key, $value );
469             }
470             elsif ( $flags & B::SVf_IOK() ) {
471             # Force numeric; fixes dual-vars comparison bug on old Win32s
472 17675         23933 $value = 0+$value;
473 17675 50 33     73638 if ( $value > $max_int64 || $value < $min_int64 ) {
    100 100        
474 0         0 croak("BSON can only handle 8-byte integers. Key '$key' is '$value'");
475             }
476             elsif ( $value > $max_int32 || $value < $min_int32 ) {
477 8811         22542 $bson .= pack( BSON_TYPE_NAME, 0x12, $utf8_key ) . _pack_int64($value);
478             }
479             else {
480 8864         30601 $bson .= pack( BSON_TYPE_NAME . BSON_INT32, 0x10, $utf8_key, $value );
481             }
482             }
483             elsif ( $opt->{prefer_numeric} && looks_like_number($value) ) {
484             # Looks like int: type heuristic based on size
485 3 100       36 if ( $value =~ $int_re ) {
    50          
486 2 50 33     25 if ( $value > $max_int64 || $value < $min_int64 ) {
    50 33        
487 0         0 croak("BSON can only handle 8-byte integers. Key '$key' is '$value'");
488             }
489             elsif ( $value > $max_int32 || $value < $min_int32 ) {
490 0         0 $bson .= pack( BSON_TYPE_NAME, 0x12, $utf8_key ) . _pack_int64($value);
491             }
492             else {
493 2         15 $bson .= pack( BSON_TYPE_NAME . BSON_INT32, 0x10, $utf8_key, $value );
494             }
495             }
496              
497             # Looks like double
498             elsif ( $value =~ $doub_re ) {
499 1         10 $bson .= pack( BSON_TYPE_NAME.BSON_DOUBLE, 0x01, $utf8_key, $value );
500             }
501              
502             # looks_like_number true, but doesn't match int/double
503             # regexes, so as a last resort we leave as string
504             else {
505 0         0 utf8::encode($value);
506 0         0 $bson .= pack( BSON_TYPE_NAME.BSON_STRING, 0x02, $utf8_key, $value );
507             }
508             }
509             else {
510             # Not coercing or didn't look like a number
511 121         296 utf8::encode($value);
512 121         601 $bson .= pack( BSON_TYPE_NAME.BSON_STRING, 0x02, $utf8_key, $value );
513             }
514             }
515             }
516              
517 29714         65172 delete $opt->{_circular}{$refaddr};
518 29714         40591 $opt->{_depth}--;
519              
520 29714         234711 return pack( BSON_INT32, length($bson) + 5 ) . $bson . "\0";
521             }
522              
523             my %FIELD_SIZES = (
524             0x01 => 8,
525             0x02 => 5,
526             0x03 => 5,
527             0x04 => 5,
528             0x05 => 5,
529             0x06 => 0,
530             0x07 => 12,
531             0x08 => 1,
532             0x09 => 8,
533             0x0A => 0,
534             0x0B => 2,
535             0x0C => 17,
536             0x0D => 5,
537             0x0E => 5,
538             0x0F => 11,
539             0x10 => 4,
540             0x11 => 8,
541             0x12 => 8,
542             0x13 => 16,
543             0x7F => 0,
544             0xFF => 0,
545             );
546              
547             my $ERR_UNSUPPORTED = "unsupported BSON type \\x%X for key '%s'. Are you using the latest version of BSON.pm?";
548             my $ERR_TRUNCATED = "premature end of BSON field '%s' (type 0x%x)";
549             my $ERR_LENGTH = "BSON field '%s' (type 0x%x) has invalid length: wanted %d, got %d";
550             my $ERR_MISSING_NULL = "BSON field '%s' (type 0x%x) missing null terminator";
551             my $ERR_BAD_UTF8 = "BSON field '%s' (type 0x%x) contains invalid UTF-8";
552             my $ERR_NEG_LENGTH = "BSON field '%s' (type 0x%x) contains negative length";
553             my $ERR_BAD_OLDBINARY = "BSON field '%s' (type 0x%x, subtype 0x02) is invalid";
554              
555             sub __dump_bson {
556 0     0   0 my $bson = unpack("H*", shift);
557 0         0 my @pairs = $bson=~ m/(..)/g;
558 0         0 return join(" ", @pairs);
559             }
560              
561             sub _decode_bson {
562 28903     28903   75913 my ($bson, $opt) = @_;
563 28903 50       57809 if ( !defined $bson ) {
564 0         0 croak("Decode argument must not be undef");
565             }
566 28903 100       52699 $opt->{_depth} = 0 unless defined $opt->{_depth};
567 28903         37303 $opt->{_depth}++;
568 28903 100       52324 if ($opt->{_depth} > BSON_MAX_DEPTH) {
569 1         690 croak "Exceeded max object depth of ". BSON_MAX_DEPTH;
570             }
571 28902         39762 my $blen= length($bson);
572 28902         52392 my $len = unpack( BSON_INT32, $bson );
573 28902 100       54648 if ( length($bson) != $len ) {
574 13         2215 croak("Incorrect length of the bson string (got $blen, wanted $len)");
575             }
576 28889 100       69793 if ( chop($bson) ne "\x00" ) {
577 5         615 croak("BSON document not null terminated");
578             }
579 28884         58769 $bson = substr $bson, 4;
580 28884         40138 my @array = ();
581 28884         39877 my %hash = ();
582 28884 100       53755 tie( %hash, 'Tie::IxHash' ) if $opt->{ordered};
583 28884         56230 my ($type, $key, $value);
584 28884         52044 while ($bson) {
585 143742         473995 ( $type, $key, $bson ) = unpack( BSON_TYPE_NAME.BSON_REMAINING, $bson );
586 143742         321385 utf8::decode($key);
587              
588             # Check type and truncation
589 143742         217388 my $min_size = $FIELD_SIZES{$type};
590 143742 100       238207 if ( !defined $min_size ) {
591 8         945 croak( sprintf( $ERR_UNSUPPORTED, $type, $key ) );
592             }
593 143734 100       224891 if ( length($bson) < $min_size ) {
594 12         2110 croak( sprintf( $ERR_TRUNCATED, $key, $type ) );
595             }
596              
597             # Double
598 143722 100 100     802441 if ( $type == 0x01 ) {
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
599 8989         24932 ( $value, $bson ) = unpack( BSON_DOUBLE.BSON_REMAINING, $bson );
600 8989 100       20801 $value = BSON::Double->new( value => $value ) if $opt->{wrap_numbers};
601             }
602              
603             # String and Symbol (deprecated); Symbol will be convert to String
604             elsif ( $type == 0x02 || $type == 0x0E ) {
605 8949         26269 ( $len, $bson ) = unpack( BSON_INT32 . BSON_REMAINING, $bson );
606 8949 100 100     36273 if ( length($bson) < $len || substr( $bson, $len - 1, 1 ) ne "\x00" ) {
607 11         1236 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
608             }
609 8938         34132 ( $value, $bson ) = unpack( "a$len" . BSON_REMAINING, $bson );
610 8938         14950 chop($value); # remove trailing \x00
611 8938 100       22167 if ( !utf8::decode($value) ) {
612 3         330 croak( sprintf( $ERR_BAD_UTF8, $key, $type ) );
613             }
614 8935 100       20035 $value = BSON::String->new( value => $value ) if $opt->{wrap_strings};
615             }
616              
617             # Document and Array
618             elsif ( $type == 0x03 || $type == 0x04 ) {
619 18096         33349 my $len = unpack( BSON_INT32, $bson );
620 18096         105771 $value = _decode_bson( substr( $bson, 0, $len ), { %$opt, _decode_array => $type == 0x04} );
621 17990 50 100     91145 if ( $opt->{wrap_dbrefs} && $type == 0x03 && exists $value->{'$id'} && exists $value->{'$ref'} ) {
      100        
      66        
622 11         145 $value = BSON::DBRef->new( %$value );
623             }
624 17990         53489 $bson = substr( $bson, $len, length($bson) - $len );
625             }
626              
627             # Binary
628             elsif ( $type == 0x05 ) {
629 8962         20823 my ( $len, $btype ) = unpack( BSON_INT32 . BSON_BINARY_TYPE, $bson );
630 8962         19013 substr( $bson, 0, 5, '' );
631              
632 8962 100       16314 if ( $len < 0 ) {
633 1         122 croak( sprintf( $ERR_NEG_LENGTH, $key, $type ) );
634             }
635 8961 100       16056 if ( $len > length($bson) ) {
636 1         239 croak( sprintf( $ERR_TRUNCATED, $key, $type ) );
637             }
638              
639 8960         15600 my $binary = substr( $bson, 0, $len, '' );
640              
641 8960 100       17335 if ( $btype == 2 ) {
642 5 50       15 if ( $len < 4 ) {
643 0         0 croak( sprintf( $ERR_BAD_OLDBINARY, $key, $type ) );
644             }
645              
646 5         11 my $sublen = unpack( BSON_INT32, $binary );
647 5 100       14 if ( $sublen != length($binary) - 4 ) {
648 3         359 croak( sprintf( $ERR_BAD_OLDBINARY, $key, $type ) );
649             }
650              
651 2         4 substr( $binary, 0, 4, '' );
652             }
653              
654 8957         179643 $value = BSON::Bytes->new( subtype => $btype, data => $binary );
655             }
656              
657             # Undef (deprecated)
658             elsif ( $type == 0x06 ) {
659 2         3 $value = undef;
660             }
661              
662             # ObjectId
663             elsif ( $type == 0x07 ) {
664 8905         26873 ( my $oid, $bson ) = unpack( BSON_OBJECTID.BSON_REMAINING, $bson );
665 8905         171323 $value = BSON::OID->new(oid => $oid);
666             }
667              
668             # Boolean
669             elsif ( $type == 0x08 ) {
670 8978         24637 ( my $bool, $bson ) = unpack( BSON_BOOLEAN.BSON_REMAINING, $bson );
671 8978 100 100     25454 croak("BSON boolean must be 0 or 1. Key '$key' is $bool")
672             unless $bool == 0 || $bool == 1;
673 8976         22860 $value = boolean( $bool );
674             }
675              
676             # Datetime
677             elsif ( $type == 0x09 ) {
678 8844         10902 if ( HAS_INT64 ) {
679 8844         27711 ($value, $bson) = unpack(BSON_INT64.BSON_REMAINING,$bson);
680             }
681             else {
682             ($value, $bson) = unpack(BSON_8BYTES.BSON_REMAINING,$bson);
683             $value = _int64_to_bigint($value);
684             }
685 8844         172206 $value = BSON::Time->new( value => $value );
686 8844         83715 my $dt_type = $opt->{dt_type};
687 8844 100 100     19750 if ( defined $dt_type && $dt_type ne 'BSON::Time' ) {
688 1 50       253 $value =
    50          
    50          
    50          
689             $dt_type eq 'Time::Moment' ? $value->as_time_moment
690             : $dt_type eq 'DateTime' ? $value->as_datetime
691             : $dt_type eq 'DateTime::Tiny' ? $value->as_datetime_tiny
692             : $dt_type eq 'Mango::BSON::Time' ? $value->as_mango_time
693             : croak("Unsupported dt_type '$dt_type'");
694             }
695             }
696              
697             # Null
698             elsif ( $type == 0x0A ) {
699 8882         12906 $value = undef;
700             }
701              
702             # Regex
703             elsif ( $type == 0x0B ) {
704 8885         30072 ( my $re, my $op, $bson ) = unpack( BSON_CSTRING.BSON_CSTRING.BSON_REMAINING, $bson );
705 8885         175227 $value = BSON::Regex->new( pattern => $re, flags => $op );
706             }
707              
708             # DBPointer (deprecated)
709             elsif ( $type == 0x0C ) {
710 11         34 ( $len, $bson ) = unpack( BSON_INT32 . BSON_REMAINING, $bson );
711 11 100 66     63 if ( length($bson) < $len || substr( $bson, $len - 1, 1 ) ne "\x00" ) {
712 3         511 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
713             }
714 8         29 ( my ($ref), $bson ) = unpack( "a$len" . BSON_REMAINING, $bson );
715 8         16 chop($ref); # remove trailing \x00
716 8 100       24 if ( !utf8::decode($ref) ) {
717 1         123 croak( sprintf( $ERR_BAD_UTF8, $key, $type ) );
718             }
719              
720 7         20 ( my ($oid), $bson ) = unpack( BSON_OBJECTID . BSON_REMAINING, $bson );
721 7         170 $value = BSON::DBRef->new( '$ref' => $ref, '$id' => BSON::OID->new( oid => $oid ) );
722             }
723              
724             # Code
725             elsif ( $type == 0x0D ) {
726 326         1561 ( $len, $bson ) = unpack( BSON_INT32 . BSON_REMAINING, $bson );
727 326 50 33     1875 if ( length($bson) < $len || substr( $bson, $len - 1, 1 ) ne "\x00" ) {
728 0         0 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
729             }
730 326         1461 ( $value, $bson ) = unpack( "a$len" . BSON_REMAINING, $bson );
731 326         710 chop($value); # remove trailing \x00
732 326 50       964 if ( !utf8::decode($value) ) {
733 0         0 croak( sprintf( $ERR_BAD_UTF8, $key, $type ) );
734             }
735 326         6646 $value = BSON::Code->new( code => $value );
736             }
737              
738             # Code with scope
739             elsif ( $type == 0x0F ) {
740 8670         16578 my $len = unpack( BSON_INT32, $bson );
741              
742             # validate length
743 8670 100       18359 if ( $len < 0 ) {
744 1         136 croak( sprintf( $ERR_NEG_LENGTH, $key, $type ) );
745             }
746 8669 100       16346 if ( $len > length($bson) ) {
747 2         301 croak( sprintf( $ERR_TRUNCATED, $key, $type ) );
748             }
749 8667 100       13808 if ( $len < 5 ) {
750 1         265 croak( sprintf( $ERR_LENGTH, $key, $type, 5, $len ) );
751             }
752              
753             # extract code and scope and chop off leading length
754 8666         21437 my $codewscope = substr( $bson, 0, $len, '' );
755 8666         12055 substr( $codewscope, 0, 4, '' );
756              
757             # extract code ( i.e. string )
758 8666         13490 my $strlen = unpack( BSON_INT32, $codewscope );
759 8666         12674 substr( $codewscope, 0, 4, '' );
760              
761 8666 100 66     28532 if ( length($codewscope) < $strlen || substr( $codewscope, -1, 1 ) ne "\x00" ) {
762 1         133 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
763             }
764              
765 8665         17285 my $code = substr($codewscope, 0, $strlen, '' );
766 8665         13902 chop($code); # remove trailing \x00
767 8665 50       22256 if ( !utf8::decode($code) ) {
768 0         0 croak( sprintf( $ERR_BAD_UTF8, $key, $type ) );
769             }
770              
771 8665 100       15805 if ( length($codewscope) < 5 ) {
772 2         256 croak( sprintf( $ERR_TRUNCATED, $key, $type ) );
773             }
774              
775             # extract scope
776 8663         15397 my $scopelen = unpack( BSON_INT32, $codewscope );
777 8663 100 66     29989 if ( length($codewscope) < $scopelen || substr( $codewscope, $scopelen - 1, 1 ) ne "\x00" ) {
778 3         393 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
779             }
780              
781 8660         49279 my $scope = _decode_bson( $codewscope, { %$opt, _decode_array => 0} );
782              
783 8659         182410 $value = BSON::Code->new( code => $code, scope => $scope );
784             }
785              
786             # Int32
787             elsif ( $type == 0x10 ) {
788 8802         26274 ( $value, $bson ) = unpack( BSON_INT32.BSON_REMAINING, $bson );
789 8802 100       19549 $value = BSON::Int32->new( value => $value ) if $opt->{wrap_numbers};
790             }
791              
792             # Timestamp
793             elsif ( $type == 0x11 ) {
794 8871         29642 ( my $sec, my $inc, $bson ) = unpack( BSON_UINT32.BSON_UINT32.BSON_REMAINING, $bson );
795 8871         172269 $value = BSON::Timestamp->new( $inc, $sec );
796             }
797              
798             # Int64
799             elsif ( $type == 0x12 ) {
800 8840         12031 if ( HAS_INT64 ) {
801 8840         26850 ($value, $bson) = unpack(BSON_INT64.BSON_REMAINING,$bson);
802             }
803             else {
804             ($value, $bson) = unpack(BSON_8BYTES.BSON_REMAINING,$bson);
805             $value = _int64_to_bigint($value);
806             }
807 8840 100       19133 $value = BSON::Int64->new( value => $value ) if $opt->{wrap_numbers};
808             }
809              
810             # Decimal128
811             elsif ( $type == 0x13 ) {
812 1206         3504 ( my $bytes, $bson ) = unpack( BSON_16BYTES.BSON_REMAINING, $bson );
813 1206         28959 $value = BSON::Decimal128->new( bytes => $bytes );
814             }
815              
816             # MinKey
817             elsif ( $type == 0xFF ) {
818 8789         22754 $value = BSON::MinKey->new;
819             }
820              
821             # MaxKey
822             elsif ( $type == 0x7F ) {
823 8715         23636 $value = BSON::MaxKey->new;
824             }
825              
826             # ???
827             else {
828             # Should have already been caught in the minimum length check,
829             # but just in case not:
830 0         0 croak( sprintf( $ERR_UNSUPPORTED, $type, $key ) );
831             }
832              
833 143577 100       294486 if ( $opt->{_decode_array} ) {
834 28546         65943 push @array, $value;
835             }
836             else {
837 115031         276823 $hash{$key} = $value;
838             }
839             }
840 28719         57543 $opt->{_depth}--;
841 28719 100       74634 return $opt->{_decode_array} ? \@array : \%hash;
842             }
843              
844             1;
845              
846             =pod
847              
848             =encoding UTF-8
849              
850             =head1 NAME
851              
852             BSON::PP - Pure Perl BSON implementation
853              
854             =head1 VERSION
855              
856             version v1.12.1
857              
858             =head1 DESCRIPTION
859              
860             This module contains the pure-Perl implementation for BSON encoding and
861             decoding. There is no public API. Use the L module and it will
862             choose the best implementation for you.
863              
864             =head1 AUTHORS
865              
866             =over 4
867              
868             =item *
869              
870             David Golden
871              
872             =item *
873              
874             Stefan G.
875              
876             =back
877              
878             =head1 COPYRIGHT AND LICENSE
879              
880             This software is Copyright (c) 2019 by Stefan G. and MongoDB, Inc.
881              
882             This is free software, licensed under:
883              
884             The Apache License, Version 2.0, January 2004
885              
886             =cut
887              
888             __END__