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   31765 use 5.010001;
  71         234  
2 71     71   339 use strict;
  71         145  
  71         1386  
3 71     71   301 use warnings;
  71         308  
  71         2177  
4 71     71   415 no warnings 'recursion';
  71         151  
  71         3176  
5              
6             package BSON::PP;
7             # ABSTRACT: Pure Perl BSON implementation
8              
9 71     71   395 use version;
  71         151  
  71         444  
10             our $VERSION = 'v1.12.0';
11              
12 71     71   5922 use B;
  71         146  
  71         3312  
13 71     71   410 use Carp;
  71         118  
  71         3747  
14 71     71   477 use Config;
  71         143  
  71         3211  
15 71     71   357 use Scalar::Util qw/blessed looks_like_number refaddr reftype/;
  71         128  
  71         4103  
16 71     71   399 use List::Util qw/first/;
  71         120  
  71         7906  
17 71     71   738 use Tie::IxHash;
  71         286053  
  71         1981  
18              
19 71     71   751 use BSON::Types ();
  71         257  
  71         1753  
20 71     71   426 use boolean;
  71         143  
  71         390  
21 71     71   4776 use mro;
  71         141  
  71         548  
22              
23 71     71   1788 use re 'regexp_pattern';
  71         163  
  71         11279  
24              
25             use constant {
26             HAS_INT64 => $Config{use64bitint},
27 71     71   456 };
  71         140  
  71         5983  
28              
29 71     71   425 use if !HAS_INT64, "Math::BigInt";
  71         164  
  71         439  
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         348177 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   18858 };
  71         147  
68              
69             sub _printable {
70 1     1   2 my $value = shift;
71 1         6 $value =~ s/([^[:print:]])/sprintf("\\x%02x",ord($1))/ge;
  1         7  
72 1         76 return $value;
73             }
74              
75             sub _split_re {
76 5     5   3999 my $value = shift;
77 5 50       34 if ( $] ge 5.010 ) {
78 5         22 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   760 my $ixhash = shift;
91 21         36 my $started = 0;
92             return sub {
93 67 100   67   148 my $k = $started ? $ixhash->NEXTKEY : do { $started++; $ixhash->FIRSTKEY };
  21         28  
  21         65  
94 67 100       483 return unless defined $k;
95 47         101 return ($k, $ixhash->FETCH($k));
96             }
97 21         92 }
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 28363     28363   36676 my $hashlike = shift;
103 28363         86236 my @keys = keys %$hashlike;
104             @keys = sort @keys
105 28363 50       197665 if $ENV{BSON_TEST_SORT_HASH};
106             return sub {
107 165753     165753   294527 my $k = shift @keys;
108 165753 100       275656 return unless defined $k;
109 137599         383536 return ($k, $hashlike->{$k});
110             }
111 28363         120899 }
112              
113             # XXX could be optimized down to only one substr to trim/pad
114             sub _bigint_to_int64 {
115 10     10   16 my $bigint = shift;
116 10         25 my $neg = $bigint < 0;
117 10 100       1573 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         106 $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         1294 my $as_hex = $bigint->as_hex; # big-endian hex
128 10         952 $as_hex =~ s{-?0x}{};
129 10         20 my $len = length($as_hex);
130 10 100       41 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       22 $pack |= "\x80\x00\x00\x00\x00\x00\x00\x00" if $neg;
133 10         52 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 16825     16825   20843 my $value = shift;
154 16825         19652 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 16825 100       22906 if ( ! $type ) {
159 16815         55494 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       43 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         2 my $type = ref($doc);
178 1         3 my $reftype = reftype($doc);
179 1 50       10 die "Can't encode non-container of type '$type'" unless $reftype eq 'HASH';
180 0         0 return;
181             }
182              
183             sub _encode_bson {
184 28514     28514   41865 my ($doc, $opt) = @_;
185              
186 28514         44324 my $refaddr = refaddr($doc);
187 28514 100       78538 die "circular reference detected" if $opt->{_circular}{$refaddr}++;
188              
189 28510 100       47604 $opt->{_depth} = 0 unless defined $opt->{_depth};
190 28510         30118 $opt->{_depth}++;
191 28510 100       44841 if ($opt->{_depth} > BSON_MAX_DEPTH) {
192 2         1143 croak "Exceeded max object depth of ". BSON_MAX_DEPTH;
193             }
194              
195 28508         36489 my $doc_type = ref($doc);
196              
197 28508 100 66     71644 if ( $doc_type eq 'BSON::Raw' || $doc_type eq 'MongoDB::BSON::_EncodedDoc' ) {
198 103         157 delete $opt->{_circular}{$refaddr};
199 103         98 $opt->{_depth}--;
200 103         382 return $doc->bson;
201             }
202              
203 28405 100       40966 if ( $doc_type eq 'MongoDB::BSON::Raw' ) {
204 3         7 delete $opt->{_circular}{$refaddr};
205 3         4 $opt->{_depth}--;
206 3         13 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 28402 50       39544 : do { _reftype_check($doc); undef };
  1 100       4  
  0 100       0  
    100          
    100          
216              
217 28401   66     62157 $iter //= _hashlike_iterator($doc);
218              
219 28401 100       51896 my $op_char = defined($opt->{op_char}) ? $opt->{op_char} : '';
220             my $invalid =
221 28401 100       45614 length( $opt->{invalid_chars} ) ? qr/[\Q$opt->{invalid_chars}\E]/ : undef;
222              
223             # Set up first key bookkeeping
224 28401         34229 my $first_key_pending = !! defined($opt->{first_key});
225 28401         28137 my $first_key;
226 28401         31829 my $bson = '';
227              
228 28401         32687 my ($key, $value);
229 28401   100     53367 while ( $first_key_pending or ( $key, $value ) = $iter->() ) {
230 137671 100 100     382805 next if defined $first_key && $key eq $first_key;
231              
232 137670 100       174471 if ( $first_key_pending ) {
233 2         3 $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 137670 50       185964 last unless defined $key;
239              
240 137670 100       210727 croak "Key '" . _printable($key) . "' contains null character"
241             unless -1 == index($key, "\0");
242              
243 137669 100 100     197404 substr( $key, 0, 1 ) = '$'
244             if length($op_char) && substr( $key, 0, 1 ) eq $op_char;
245              
246 137669 100 66     182825 if ( $invalid && $key =~ $invalid ) {
247             croak(
248             sprintf(
249             "key '%s' has invalid character(s) '%s'",
250             $key, $opt->{invalid_chars}
251             )
252 2         162 );
253             }
254              
255 137667         148985 my $utf8_key = $key;
256 137667         243972 utf8::encode($utf8_key);
257 137667         180461 my $type = ref $value;
258              
259             # If the type is a subtype of BSON::*, use that instead
260 137667 100       239438 if ( blessed $value ) {
261 86247 100       223051 if ($type !~ /\ABSON::\w+\z/) {
262 39     57   134 my $parent = first { /\ABSON::\w+\z/ } reverse @{mro::get_linear_isa($type)};
  57         123  
  39         244  
263 39 50       160 $type = $parent if defined $parent;
264             }
265             }
266              
267             # Null
268 137667 100       232709 if ( !defined $value ) {
    100          
269 8367         21377 $bson .= pack( BSON_TYPE_NAME, 0x0A, $utf8_key );
270             }
271              
272             # REFERENCES/OBJECTS
273             elsif ( length $type ) {
274              
275             # Array
276 103889 100 100     1356273 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 8425         8682 my $i = 0;
278 8425         27643 tie( my %h, 'Tie::IxHash' );
279 8425         117592 %h = map { $i++ => $_ } @$value;
  25179         37908  
280 8425         392803 $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         6 my %data;
286 4         13 tie %data, 'Tie::IxHash';
287 4         56 $data{'$ref'} = $value->{'ref'};
288 4         50 $data{'$id'} = $value->{id};
289 4         52 $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 9341         23004 $bson .= pack( BSON_TYPE_NAME, 0x03, $utf8_key ) . _encode_bson($value, $opt);
304             }
305              
306             # Regex
307             elsif ( $type eq 'Regexp' ) {
308 4         9 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 8389         10798 my ( $re, $flags ) = @{$value}{qw/pattern flags/};
  8389         20230  
313 8389         28835 $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 8553         36508 $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 8434         26640 $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 8376         40324 $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 8288         24631 $bson .= pack( BSON_TYPE_NAME, 0xFF, $utf8_key );
360             }
361              
362             # MaxKey
363             elsif ( $type eq 'BSON::MaxKey' || $type eq 'MongoDB::MaxKey' ) {
364 8445         25281 $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 8425 50       22834 : pack( "C*", @{ $value->data } );
  4 100       11  
    100          
378 8425 100       14765 my $subtype = $type eq 'SCALAR' ? 0 : $value->subtype;
379 8425         9721 my $len = length($data);
380 8425 100       10496 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 8423         31677 $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 8530         20990 my $code = $value->code;
394 8530         27349 utf8::encode($code);
395 8530         26125 $code = pack(BSON_STRING,$code);
396 8530 100       20694 if ( ref( $value->scope ) eq 'HASH' ) {
397 8262         14270 my $scope = _encode_bson( $value->scope, $opt );
398 8262         33791 $bson .=
399             pack( BSON_TYPE_NAME.BSON_CODE_W_SCOPE, 0x0F, $utf8_key, (4 + length($scope) + length($code)) ) . $code . $scope;
400             }
401             else {
402 268         1010 $bson .= pack( BSON_TYPE_NAME, 0x0D, $utf8_key) . $code;
403             }
404             }
405              
406             # Boolean
407             elsif ( $type eq 'boolean' || $type =~ $bools_re ) {
408 8425 100       45575 $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 8644         20909 $value = $value->value;
414 8644         21389 utf8::encode($value);
415 8644         33247 $bson .= pack( BSON_TYPE_NAME.BSON_STRING, 0x02, $utf8_key, $value );
416             }
417             elsif ( $type eq 'MongoDB::BSON::String' ) {
418 2         7 $value = $$value;
419 2         7 utf8::encode($value);
420 2         15 $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     389 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       2179 if ( $type eq 'BSON::Int64' ) {
432 16         50 $value = $value->value;
433             }
434              
435 24         110 $bson .= pack( BSON_TYPE_NAME, 0x12, $utf8_key ) . _pack_int64($value);
436             }
437              
438             elsif ( $type eq 'BSON::Int32' ) {
439 35         251 $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         656 $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         27900 $bson .= pack( BSON_TYPE_NAME.BSON_16BYTES, 0x13, $utf8_key, $value->bytes );
450             }
451              
452             # Unsupported type
453             else {
454 1         166 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 25411         72168 my $flags = B::svref_2object(\$value)->FLAGS;
466              
467 25411 100 100     51379 if ( $flags & B::SVf_NOK() ) {
    100          
    100          
468 8419         25343 $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 16868         18445 $value = 0+$value;
473 16868 50 33     58420 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 8367         19132 $bson .= pack( BSON_TYPE_NAME, 0x12, $utf8_key ) . _pack_int64($value);
478             }
479             else {
480 8501         25637 $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       32 if ( $value =~ $int_re ) {
    50          
486 2 50 33     19 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         13 $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         9 $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         271 utf8::encode($value);
512 121         611 $bson .= pack( BSON_TYPE_NAME.BSON_STRING, 0x02, $utf8_key, $value );
513             }
514             }
515             }
516              
517 28191         53526 delete $opt->{_circular}{$refaddr};
518 28191         29810 $opt->{_depth}--;
519              
520 28191         189960 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 27381     27381   58366 my ($bson, $opt) = @_;
563 27381 50       40478 if ( !defined $bson ) {
564 0         0 croak("Decode argument must not be undef");
565             }
566 27381 100       43318 $opt->{_depth} = 0 unless defined $opt->{_depth};
567 27381         28750 $opt->{_depth}++;
568 27381 100       41446 if ($opt->{_depth} > BSON_MAX_DEPTH) {
569 1         576 croak "Exceeded max object depth of ". BSON_MAX_DEPTH;
570             }
571 27380         30540 my $blen= length($bson);
572 27380         44084 my $len = unpack( BSON_INT32, $bson );
573 27380 100       41186 if ( length($bson) != $len ) {
574 13         1784 croak("Incorrect length of the bson string (got $blen, wanted $len)");
575             }
576 27367 100       53186 if ( chop($bson) ne "\x00" ) {
577 5         583 croak("BSON document not null terminated");
578             }
579 27362         45942 $bson = substr $bson, 4;
580 27362         30932 my @array = ();
581 27362         29221 my %hash = ();
582 27362 100       45544 tie( %hash, 'Tie::IxHash' ) if $opt->{ordered};
583 27362         45878 my ($type, $key, $value);
584 27362         40248 while ($bson) {
585 136525         377216 ( $type, $key, $bson ) = unpack( BSON_TYPE_NAME.BSON_REMAINING, $bson );
586 136525         248849 utf8::decode($key);
587              
588             # Check type and truncation
589 136525         170517 my $min_size = $FIELD_SIZES{$type};
590 136525 100       182854 if ( !defined $min_size ) {
591 8         941 croak( sprintf( $ERR_UNSUPPORTED, $type, $key ) );
592             }
593 136517 100       179362 if ( length($bson) < $min_size ) {
594 12         2007 croak( sprintf( $ERR_TRUNCATED, $key, $type ) );
595             }
596              
597             # Double
598 136505 100 100     644899 if ( $type == 0x01 ) {
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
599 8465         19675 ( $value, $bson ) = unpack( BSON_DOUBLE.BSON_REMAINING, $bson );
600 8465 100       16805 $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 8776         21428 ( $len, $bson ) = unpack( BSON_INT32 . BSON_REMAINING, $bson );
606 8776 100 100     29686 if ( length($bson) < $len || substr( $bson, $len - 1, 1 ) ne "\x00" ) {
607 11         1148 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
608             }
609 8765         28078 ( $value, $bson ) = unpack( "a$len" . BSON_REMAINING, $bson );
610 8765         12364 chop($value); # remove trailing \x00
611 8765 100       17391 if ( !utf8::decode($value) ) {
612 3         327 croak( sprintf( $ERR_BAD_UTF8, $key, $type ) );
613             }
614 8762 100       14671 $value = BSON::String->new( value => $value ) if $opt->{wrap_strings};
615             }
616              
617             # Document and Array
618             elsif ( $type == 0x03 || $type == 0x04 ) {
619 16971         25812 my $len = unpack( BSON_INT32, $bson );
620 16971         87881 $value = _decode_bson( substr( $bson, 0, $len ), { %$opt, _decode_array => $type == 0x04} );
621 16865 50 100     74545 if ( $opt->{wrap_dbrefs} && $type == 0x03 && exists $value->{'$id'} && exists $value->{'$ref'} ) {
      100        
      66        
622 11         134 $value = BSON::DBRef->new( %$value );
623             }
624 16865         43578 $bson = substr( $bson, $len, length($bson) - $len );
625             }
626              
627             # Binary
628             elsif ( $type == 0x05 ) {
629 8427         15908 my ( $len, $btype ) = unpack( BSON_INT32 . BSON_BINARY_TYPE, $bson );
630 8427         13915 substr( $bson, 0, 5, '' );
631              
632 8427 100       13501 if ( $len < 0 ) {
633 1         101 croak( sprintf( $ERR_NEG_LENGTH, $key, $type ) );
634             }
635 8426 100       13126 if ( $len > length($bson) ) {
636 1         214 croak( sprintf( $ERR_TRUNCATED, $key, $type ) );
637             }
638              
639 8425         13747 my $binary = substr( $bson, 0, $len, '' );
640              
641 8425 100       12798 if ( $btype == 2 ) {
642 5 50       13 if ( $len < 4 ) {
643 0         0 croak( sprintf( $ERR_BAD_OLDBINARY, $key, $type ) );
644             }
645              
646 5         8 my $sublen = unpack( BSON_INT32, $binary );
647 5 100       14 if ( $sublen != length($binary) - 4 ) {
648 3         334 croak( sprintf( $ERR_BAD_OLDBINARY, $key, $type ) );
649             }
650              
651 2         4 substr( $binary, 0, 4, '' );
652             }
653              
654 8422         139551 $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 8546         20971 ( my $oid, $bson ) = unpack( BSON_OBJECTID.BSON_REMAINING, $bson );
665 8546         138126 $value = BSON::OID->new(oid => $oid);
666             }
667              
668             # Boolean
669             elsif ( $type == 0x08 ) {
670 8427         19558 ( my $bool, $bson ) = unpack( BSON_BOOLEAN.BSON_REMAINING, $bson );
671 8427 100 100     19808 croak("BSON boolean must be 0 or 1. Key '$key' is $bool")
672             unless $bool == 0 || $bool == 1;
673 8425         18088 $value = boolean( $bool );
674             }
675              
676             # Datetime
677             elsif ( $type == 0x09 ) {
678 8437         8867 if ( HAS_INT64 ) {
679 8437         20741 ($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 8437         138804 $value = BSON::Time->new( value => $value );
686 8437         69138 my $dt_type = $opt->{dt_type};
687 8437 100 100     17385 if ( defined $dt_type && $dt_type ne 'BSON::Time' ) {
688 1 50       198 $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 8359         10618 $value = undef;
700             }
701              
702             # Regex
703             elsif ( $type == 0x0B ) {
704 8393         24845 ( my $re, my $op, $bson ) = unpack( BSON_CSTRING.BSON_CSTRING.BSON_REMAINING, $bson );
705 8393         138749 $value = BSON::Regex->new( pattern => $re, flags => $op );
706             }
707              
708             # DBPointer (deprecated)
709             elsif ( $type == 0x0C ) {
710 11         28 ( $len, $bson ) = unpack( BSON_INT32 . BSON_REMAINING, $bson );
711 11 100 66     47 if ( length($bson) < $len || substr( $bson, $len - 1, 1 ) ne "\x00" ) {
712 3         407 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
713             }
714 8         28 ( my ($ref), $bson ) = unpack( "a$len" . BSON_REMAINING, $bson );
715 8         13 chop($ref); # remove trailing \x00
716 8 100       18 if ( !utf8::decode($ref) ) {
717 1         107 croak( sprintf( $ERR_BAD_UTF8, $key, $type ) );
718             }
719              
720 7         15 ( my ($oid), $bson ) = unpack( BSON_OBJECTID . BSON_REMAINING, $bson );
721 7         141 $value = BSON::DBRef->new( '$ref' => $ref, '$id' => BSON::OID->new( oid => $oid ) );
722             }
723              
724             # Code
725             elsif ( $type == 0x0D ) {
726 268         867 ( $len, $bson ) = unpack( BSON_INT32 . BSON_REMAINING, $bson );
727 268 50 33     1313 if ( length($bson) < $len || substr( $bson, $len - 1, 1 ) ne "\x00" ) {
728 0         0 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
729             }
730 268         1068 ( $value, $bson ) = unpack( "a$len" . BSON_REMAINING, $bson );
731 268         427 chop($value); # remove trailing \x00
732 268 50       750 if ( !utf8::decode($value) ) {
733 0         0 croak( sprintf( $ERR_BAD_UTF8, $key, $type ) );
734             }
735 268         4671 $value = BSON::Code->new( code => $value );
736             }
737              
738             # Code with scope
739             elsif ( $type == 0x0F ) {
740 8273         13657 my $len = unpack( BSON_INT32, $bson );
741              
742             # validate length
743 8273 100       13883 if ( $len < 0 ) {
744 1         113 croak( sprintf( $ERR_NEG_LENGTH, $key, $type ) );
745             }
746 8272 100       13315 if ( $len > length($bson) ) {
747 2         244 croak( sprintf( $ERR_TRUNCATED, $key, $type ) );
748             }
749 8270 100       11076 if ( $len < 5 ) {
750 1         198 croak( sprintf( $ERR_LENGTH, $key, $type, 5, $len ) );
751             }
752              
753             # extract code and scope and chop off leading length
754 8269         16979 my $codewscope = substr( $bson, 0, $len, '' );
755 8269         10824 substr( $codewscope, 0, 4, '' );
756              
757             # extract code ( i.e. string )
758 8269         11647 my $strlen = unpack( BSON_INT32, $codewscope );
759 8269         10247 substr( $codewscope, 0, 4, '' );
760              
761 8269 100 66     21792 if ( length($codewscope) < $strlen || substr( $codewscope, -1, 1 ) ne "\x00" ) {
762 1         109 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
763             }
764              
765 8268         15567 my $code = substr($codewscope, 0, $strlen, '' );
766 8268         10588 chop($code); # remove trailing \x00
767 8268 50       17810 if ( !utf8::decode($code) ) {
768 0         0 croak( sprintf( $ERR_BAD_UTF8, $key, $type ) );
769             }
770              
771 8268 100       11933 if ( length($codewscope) < 5 ) {
772 2         209 croak( sprintf( $ERR_TRUNCATED, $key, $type ) );
773             }
774              
775             # extract scope
776 8266         11292 my $scopelen = unpack( BSON_INT32, $codewscope );
777 8266 100 66     22698 if ( length($codewscope) < $scopelen || substr( $codewscope, $scopelen - 1, 1 ) ne "\x00" ) {
778 3         369 croak( sprintf( $ERR_MISSING_NULL, $key, $type ) );
779             }
780              
781 8263         38391 my $scope = _decode_bson( $codewscope, { %$opt, _decode_array => 0} );
782              
783 8262         144209 $value = BSON::Code->new( code => $code, scope => $scope );
784             }
785              
786             # Int32
787             elsif ( $type == 0x10 ) {
788 8440         21226 ( $value, $bson ) = unpack( BSON_INT32.BSON_REMAINING, $bson );
789 8440 100       16066 $value = BSON::Int32->new( value => $value ) if $opt->{wrap_numbers};
790             }
791              
792             # Timestamp
793             elsif ( $type == 0x11 ) {
794 8375         23172 ( my $sec, my $inc, $bson ) = unpack( BSON_UINT32.BSON_UINT32.BSON_REMAINING, $bson );
795 8375         137503 $value = BSON::Timestamp->new( $inc, $sec );
796             }
797              
798             # Int64
799             elsif ( $type == 0x12 ) {
800 8396         8963 if ( HAS_INT64 ) {
801 8396         20070 ($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 8396 100       15260 $value = BSON::Int64->new( value => $value ) if $opt->{wrap_numbers};
808             }
809              
810             # Decimal128
811             elsif ( $type == 0x13 ) {
812 1206         2914 ( my $bytes, $bson ) = unpack( BSON_16BYTES.BSON_REMAINING, $bson );
813 1206         25138 $value = BSON::Decimal128->new( bytes => $bytes );
814             }
815              
816             # MinKey
817             elsif ( $type == 0xFF ) {
818 8288         20859 $value = BSON::MinKey->new;
819             }
820              
821             # MaxKey
822             elsif ( $type == 0x7F ) {
823 8445         20946 $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 136360 100       241168 if ( $opt->{_decode_array} ) {
834 25069         49035 push @array, $value;
835             }
836             else {
837 111291         224239 $hash{$key} = $value;
838             }
839             }
840 27197         47584 $opt->{_depth}--;
841 27197 100       61544 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.0
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__