File Coverage

lib/Net/BitTorrent/Protocol/BEP53.pm
Criterion Covered Total %
statement 34 53 64.1
branch 6 14 42.8
condition 7 11 63.6
subroutine 6 7 85.7
pod 1 1 100.0
total 54 86 62.7


line stmt bran cond sub pod time code
1 20     20   88870 use v5.40;
  20         86  
2 20     20   127 use feature 'class';
  20         42  
  20         3061  
3 20     20   134 no warnings 'experimental::class';
  20         59  
  20         1197  
4 20     20   585 use Net::BitTorrent::Emitter;
  20         59  
  20         2476  
5             #
6             class Net::BitTorrent::Protocol::BEP53 v2.0.0 : isa(Net::BitTorrent::Emitter) {
7 20     20   666 use URI::Escape qw[uri_unescape uri_escape];
  20         1584  
  20         30259  
8             #
9             field $infohash_v1 : reader : param = undef;
10             field $infohash_v2 : reader : param = undef;
11             field $trackers : reader : param = [];
12             field $name : reader : param = undef;
13             field $nodes : reader : param = []; # DHT bootstrap nodes (x.pe)
14              
15             #
16 13     13 1 262062 sub parse ( $class, $uri ) {
  13         31  
  13         26  
  13         23  
17 13 50       85 die 'Not a magnet URI' unless $uri =~ /^magnet:\?/;
18 13         37 my %params;
19 13         46 my $query = substr( $uri, 8 );
20 13         75 for my $pair ( split( /[&;]/, $query ) ) {
21 19         86 my ( $key, $val ) = split( /=/, $pair, 2 );
22 19 50 33     107 next unless defined $key && defined $val;
23 19         123 $val = uri_unescape($val);
24 19         203 push @{ $params{$key} }, $val;
  19         99  
25             }
26 13         30 my ( $v1, $v2 );
27 13   50     33 for my $xt ( @{ $params{xt} // [] } ) {
  13         81  
28 15 100       102 if ( $xt =~ /^urn:btih:([a-fA-F0-9]{40})$/ ) {
    50          
    50          
29 12         120 $v1 = pack( 'H*', $1 );
30             }
31             elsif ( $xt =~ /^urn:btih:([a-zA-Z2-7]{32})$/ ) {
32              
33             # Base32 encoded (v1)
34 0         0 $v1 = _decode_base32($1);
35             }
36             elsif ( $xt =~ /^urn:btmh:1220([a-fA-F0-9]{64})$/ ) {
37              
38             # BEP 53 v2 (multihash SHA-256)
39 3         16 $v2 = pack( 'H*', $1 );
40             }
41             }
42             return $class->new(
43             infohash_v1 => $v1,
44             infohash_v2 => $v2,
45             trackers => ( $params{tr} // [] ),
46             name => ( $params{dn}[0] // undef ),
47 13   50     371 nodes => ( $params{'x.pe'} // [] ),
      100        
      100        
48             );
49             }
50              
51 0     0     sub _decode_base32 ($str) {
  0            
  0            
52 0           $str = uc($str);
53 0           my $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
54 0           my %map;
55 0           @map{ split //, $alphabet } = 0 .. 31;
56 0           my $buffer = 0;
57 0           my $bits = 0;
58 0           my $res = '';
59 0           for my $char ( split //, $str ) {
60 0 0         next unless exists $map{$char};
61 0           $buffer = ( $buffer << 5 ) | $map{$char};
62 0           $bits += 5;
63 0 0         if ( $bits >= 8 ) {
64 0           $bits -= 8;
65 0           $res .= chr( ( $buffer >> $bits ) & 0xFF );
66             }
67             }
68 0           return $res;
69             }
70              
71             method to_string () {
72             my @pairs;
73             push @pairs, 'xt=urn:btih:' . unpack( 'H*', $infohash_v1 ) if $infohash_v1;
74             push @pairs, 'xt=urn:btmh:1220' . unpack( 'H*', $infohash_v2 ) if $infohash_v2;
75             push @pairs, 'dn=' . uri_escape($name) if defined $name;
76             push @pairs, 'tr=' . uri_escape($_) for @$trackers;
77             push @pairs, 'x.pe=' . uri_escape($_) for @$nodes;
78             'magnet:?' . join( '&', @pairs );
79             }
80             };
81             #
82             1;