File Coverage

blib/lib/Bencode.pm
Criterion Covered Total %
statement 74 75 98.6
branch 55 66 83.3
condition 14 14 100.0
subroutine 11 11 100.0
pod 2 2 100.0
total 156 168 92.8


line stmt bran cond sub pod time code
1 2     2   1814268 use 5.006;
  2         8  
  2         279  
2 2     2   17 use strict;
  2         4  
  2         111  
3 2     2   15 use warnings;
  2         3  
  2         148  
4              
5             package Bencode;
6             $Bencode::VERSION = '1.402';
7             # ABSTRACT: BitTorrent serialisation format
8              
9 2     2   14 use Carp;
  2         4  
  2         197  
10 2     2   18328 use Exporter::Tidy all => [qw( bencode bdecode )];
  2         39  
  2         20  
11              
12             our ( $DEBUG, $do_lenient_decode, $max_depth );
13              
14 38   100 38   13397 sub _msg { sprintf "@_", pos() || 0 }
15              
16             sub _bdecode_string {
17              
18 139 100   139   1102 if ( m/ \G ( 0 | [1-9] \d* ) : /xgc ) {
19 57         199 my $len = $1;
20              
21 57 100       273 croak _msg 'unexpected end of string data starting at %s'
22             if $len > length() - pos();
23              
24 55         153 my $str = substr $_, pos(), $len;
25 55         174 pos() = pos() + $len;
26              
27 55 0       187 warn _msg STRING => "(length $len)", $len < 200 ? "[$str]" : () if $DEBUG;
    50          
28              
29 55         204 return $str;
30             }
31             else {
32 82         219 my $pos = pos();
33 82 100       5169 if ( m/ \G -? 0? \d+ : /xgc ) {
34 4         18 pos() = $pos;
35 4         112 croak _msg 'malformed string length at %s';
36             }
37             }
38              
39 78         1086 return;
40             }
41              
42             sub _bdecode_chunk {
43 113 50   113   334 warn _msg 'decoding at %s' if $DEBUG;
44              
45 113 100       341 local $max_depth = $max_depth - 1 if defined $max_depth;
46              
47 113 100       265 if ( defined( my $str = _bdecode_string() ) ) {
    100          
    100          
    100          
48 30         141 return $str;
49             }
50             elsif ( m/ \G i /xgc ) {
51 22 100       111 croak _msg 'unexpected end of data at %s' if m/ \G \z /xgc;
52              
53 21 100       159 m/ \G ( 0 | -? [1-9] \d* ) e /xgc
54             or croak _msg 'malformed integer data at %s';
55              
56 14 50       39 warn _msg INTEGER => $1 if $DEBUG;
57 14         86 return $1;
58             }
59             elsif ( m/ \G l /xgc ) {
60 24 50       97 warn _msg 'LIST' if $DEBUG;
61              
62 24 100 100     171 croak _msg 'nesting depth exceeded at %s'
63             if defined $max_depth and $max_depth < 0;
64              
65 20         53 my @list;
66 20         112 until ( m/ \G e /xgc ) {
67 27 50       87 warn _msg 'list not terminated at %s, looking for another element' if $DEBUG;
68 27         88 push @list, _bdecode_chunk();
69             }
70 14         53 return \@list;
71             }
72             elsif ( m/ \G d /xgc ) {
73 25 50       105 warn _msg 'DICT' if $DEBUG;
74              
75 25 100 100     203 croak _msg 'nesting depth exceeded at %s'
76             if defined $max_depth and $max_depth < 0;
77              
78 22         28 my $last_key;
79             my %hash;
80 22         123 until ( m/ \G e /xgc ) {
81 28 50       97 warn _msg 'dict not terminated at %s, looking for another pair' if $DEBUG;
82              
83 28 100       137 croak _msg 'unexpected end of data at %s'
84             if m/ \G \z /xgc;
85              
86 26         61 my $key = _bdecode_string();
87 26 100       77 defined $key or croak _msg 'dict key is not a string at %s';
88              
89 25 100       76 croak _msg 'duplicate dict key at %s'
90             if exists $hash{ $key };
91              
92 24 100 100     211 croak _msg 'dict key not in sort order at %s'
      100        
93             if not( $do_lenient_decode ) and defined $last_key and $key lt $last_key;
94              
95 23 100       135 croak _msg 'dict key is missing value at %s'
96             if m/ \G e /xgc;
97              
98 22         38 $last_key = $key;
99 22         48 $hash{ $key } = _bdecode_chunk();
100             }
101 12         47 return \%hash;
102             }
103             else {
104 6 100       43 croak _msg m/ \G \z /xgc ? 'unexpected end of data at %s' : 'garbage at %s';
105             }
106             }
107              
108             sub bdecode {
109 64     64 1 102470 local $_ = shift;
110 64         217 local $do_lenient_decode = shift;
111 64         375 local $max_depth = shift;
112 64         232 my $deserialised_data = _bdecode_chunk();
113 31 100       158 croak _msg 'trailing garbage at %s' if $_ !~ m/ \G \z /xgc;
114 26         174 return $deserialised_data;
115             }
116              
117             sub _bencode {
118 32     32   42 my ( $data ) = @_;
119 32 100       111 if ( not ref $data ) {
    100          
    100          
    50          
120 17 100       202 return sprintf 'i%se', $data if $data =~ m/\A (?: 0 | -? [1-9] \d* ) \z/x;
121 6         44 return length( $data ) . ':' . $data;
122             }
123             elsif ( ref $data eq 'SCALAR' ) {
124             # escape hatch -- use this to avoid num/str heuristics
125 6         32 return length( $$data ) . ':' . $$data;
126             }
127             elsif ( ref $data eq 'ARRAY' ) {
128 5         28 return 'l' . join( '', map _bencode( $_ ), @$data ) . 'e';
129             }
130             elsif ( ref $data eq 'HASH' ) {
131 4         29 return 'd' . join( '', map { _bencode( \$_ ), _bencode( $data->{ $_ } ) } sort keys %$data ) . 'e';
  5         13  
132             }
133             else {
134 0         0 croak 'unhandled data type';
135             }
136             }
137              
138             sub bencode {
139 13 50   13 1 14792 croak 'need exactly one argument' if @_ != 1;
140 13         57 goto &_bencode;
141             }
142              
143             bdecode( 'i1e' );
144              
145             __END__