| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 2 |  |  | 2 |  | 42244 | use strict; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 120 |  | 
| 2 | 2 |  |  | 2 |  | 14 | use warnings; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 70 |  | 
| 3 | 2 |  |  | 2 |  | 1495 | use utf8; | 
|  | 2 |  |  |  |  | 29 |  | 
|  | 2 |  |  |  |  | 10 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | package Ed2k_link; | 
| 6 |  |  |  |  |  |  | $Ed2k_link::VERSION = '20160412'; | 
| 7 |  |  |  |  |  |  |  | 
| 8 | 2 |  |  | 2 |  | 113 | use Carp (); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 37 |  | 
| 9 | 2 |  |  | 2 |  | 10 | use File::Basename (); | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 33 |  | 
| 10 | 2 |  |  | 2 |  | 1238 | use URI::Escape (); | 
|  | 2 |  |  |  |  | 3008 |  | 
|  | 2 |  |  |  |  | 59 |  | 
| 11 | 2 |  |  | 2 |  | 1321 | use Encode::Locale (); | 
|  | 2 |  |  |  |  | 31106 |  | 
|  | 2 |  |  |  |  | 56 |  | 
| 12 | 2 |  |  | 2 |  | 13 | use Encode (); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 36 |  | 
| 13 | 2 |  |  | 2 |  | 1245 | use Digest::MD4 (); | 
|  | 2 |  |  |  |  | 1732 |  | 
|  | 2 |  |  |  |  | 58 |  | 
| 14 | 2 |  |  | 2 |  | 1408 | use Digest::SHA (); | 
|  | 2 |  |  |  |  | 7396 |  | 
|  | 2 |  |  |  |  | 87 |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | use constant { | 
| 17 | 2 |  |  |  |  | 6247 | CHUNK_SIZE => 9_728_000, | 
| 18 |  |  |  |  |  |  | BLOCK_SIZE => 184_320, | 
| 19 | 2 |  |  | 2 |  | 14 | }; | 
|  | 2 |  |  |  |  | 3 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | =head1 NAME | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | Ed2k_link - module for creating eD2K links and working with them. | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | =head1 VERSION | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | version 20160412 | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | use Ed2k_link (); | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | print Ed2k_link -> from_file( 'c:\\temp\\new_movie.mkv' ) -> link( 'h' ) . "\n"; | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | my $emule = Ed2k_link -> from_file( 'eMule0.49c.zip' ) or die 'something wrong with file!'); | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | my $sources = Ed2k_link -> from_link( 'ed2k://|file|eMule0.49c.zip|2868871|0F88EEFA9D8AD3F43DABAC9982D2450C|/' ) or die 'incorrect link!'; | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | $sources -> from_link( 'ed2k://|file|eMule0.49c-Sources.zip|5770302|195B6D8286BF184C3CC0665148D746CF|/' ) or die 'incorrect link!'; | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | print $emule -> link( 'h' ) if $emule -> filesize <= 10 * 1024 * 1024, "\n"; | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | if ( Ed2k_link -> equal( $emule, $sources ) { | 
| 44 |  |  |  |  |  |  | printf "files %s and %s are equal\n"; | 
| 45 |  |  |  |  |  |  | $emule -> filename, | 
| 46 |  |  |  |  |  |  | $sources -> filename; | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | print Ed2k_link -> from_file( '/somethere/cool_file.txt' ) -> link('hp'); | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | Ed2k_link module for creating eD2K links from files with correct hash, AICH hash and complete hashset fields. | 
| 54 |  |  |  |  |  |  | Also it can work with already created links (e. g. from textfile). | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | =cut | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | sub _encode_base32 { | 
| 59 | 0 |  |  | 0 |  | 0 | my %bits_to_char = qw# 00000 A 00001 B 00010 C 00011 D 00100 E 00101 F 00110 G 00111 H | 
| 60 |  |  |  |  |  |  | 01000 I 01001 J 01010 K 01011 L 01100 M 01101 N 01110 O 01111 P | 
| 61 |  |  |  |  |  |  | 10000 Q 10001 R 10010 S 10011 T 10100 U 10101 V 10110 W 10111 X | 
| 62 |  |  |  |  |  |  | 11000 Y 11001 Z 11010 2 11011 3 11100 4 11101 5 11110 6 11111 7 | 
| 63 |  |  |  |  |  |  | #; | 
| 64 | 0 |  |  |  |  | 0 | my ($source, $bits, $res) = shift; | 
| 65 | 0 |  |  |  |  | 0 | $bits .= unpack('B*', substr($source, $_, 1)) for 0 .. length($source) - 1; | 
| 66 |  |  |  |  |  |  | # generally $bits length could be not 40 * k and there has to be padding.  not our case | 
| 67 | 0 |  |  |  |  | 0 | $res .= $bits_to_char{$&} while $bits =~ m/.{5}/g; | 
| 68 | 0 |  |  |  |  | 0 | $res; | 
| 69 |  |  |  |  |  |  | } | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | sub _define_base_trees_orientation { # l/r, array_ref, start_idx, end_idx | 
| 72 | 0 | 0 |  | 0 |  | 0 | if ($_[2] - $_[3] >= 0) { | 
|  |  | 0 |  |  |  |  |  | 
| 73 | 0 |  |  |  |  | 0 | $_[1][$_[2]] = $_[0]; | 
| 74 |  |  |  |  |  |  | } elsif ($_[2] + 1 == $_[3]) { | 
| 75 | 0 |  |  |  |  | 0 | $_[1][$_[2]] = 'l'; | 
| 76 | 0 |  |  |  |  | 0 | $_[1][$_[3]] = 'r'; | 
| 77 |  |  |  |  |  |  | } else { | 
| 78 | 0 |  |  |  |  | 0 | my $med = sprintf("%d", ($_[2] + $_[3]) / 2); | 
| 79 | 0 | 0 | 0 |  |  | 0 | -- $med if $_[ 0 ] eq 'r' && $_[ 2 ] + $_[ 3 ] == $med * 2; | 
| 80 | 0 |  |  |  |  | 0 | &_define_base_trees_orientation( 'l', $_[ 1 ], $_[ 2 ], $med ); | 
| 81 | 0 |  |  |  |  | 0 | &_define_base_trees_orientation( 'r', $_[ 1 ], ++ $med, $_[ 3 ] ); | 
| 82 |  |  |  |  |  |  | } | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | sub _get_root_hash {            # l/r, array_ref, start_idx, end_idx | 
| 86 | 0 |  |  | 0 |  | 0 | my $med = $_[3]; | 
| 87 | 0 | 0 |  |  |  | 0 | if ($_[2] - $_[3] >= 0) { | 
|  |  | 0 |  |  |  |  |  | 
| 88 | 0 |  |  |  |  | 0 | return; | 
| 89 |  |  |  |  |  |  | } elsif ($_[3] - $_[2] > 1) { | 
| 90 | 0 |  |  |  |  | 0 | $med = sprintf("%d", ($_[2] + $_[3]) / 2); | 
| 91 | 0 | 0 | 0 |  |  | 0 | -- $med if $_[ 0 ] eq 'r' && $_[ 2 ] + $_[ 3 ] == $med * 2; | 
| 92 | 0 |  |  |  |  | 0 | &_get_root_hash( 'l', | 
| 93 |  |  |  |  |  |  | $_[ 1 ], | 
| 94 |  |  |  |  |  |  | $_[ 2 ], | 
| 95 |  |  |  |  |  |  | $med | 
| 96 |  |  |  |  |  |  | ); | 
| 97 | 0 |  |  |  |  | 0 | &_get_root_hash( 'r', | 
| 98 |  |  |  |  |  |  | $_[ 1 ], | 
| 99 |  |  |  |  |  |  | ++ $med, | 
| 100 |  |  |  |  |  |  | $_[ 3 ] | 
| 101 |  |  |  |  |  |  | ); | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 0 |  |  |  |  | 0 | $_[ 1 ] -> [ $_[ 2 ] ] = Digest::SHA::sha1( $_[ 1 ] -> [ $_[ 2 ] ], | 
| 105 |  |  |  |  |  |  | $_[ 1 ] -> [ $med ] | 
| 106 |  |  |  |  |  |  | ); | 
| 107 |  |  |  |  |  |  | } | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | =head1 CLASS METHODS | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | =head2 from_file | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | Can be called as class or instance method: | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | my $t = Ed2k_link -> from_file( 'file_1.txt' ) or die 'error!'; | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | $t -> from_file( 'file_2.txt' ) or die 'error!'; | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | Creates all fields of eD2K link including hash, AICH hashset, complete hashset. | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | Filename should be a character string (as opposed to octet string).  In case of any error returns undef and object doesn't hold any link information. | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | Sets Reliable flag to true. | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | =cut | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | sub from_file { | 
| 128 | 0 |  |  | 0 | 1 | 0 | my $either = shift; | 
| 129 | 0 | 0 |  |  |  | 0 | %$either = () if ref $either; | 
| 130 | 0 |  |  |  |  | 0 | my $file = shift;       # string of characters (not an octet stream) | 
| 131 | 0 | 0 |  |  |  | 0 | return undef unless defined $file; | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | # file must exist and be not empty! | 
| 134 | 0 |  |  |  |  | 0 | my $filename_to_access = Encode::encode( locale_fs => $file ); | 
| 135 | 0 | 0 | 0 |  |  | 0 | return undef unless -f $filename_to_access && -s _; | 
| 136 |  |  |  |  |  |  |  | 
| 137 | 0 |  |  |  |  | 0 | my $self = { path_to_file => $file, | 
| 138 |  |  |  |  |  |  | size => -s _, | 
| 139 |  |  |  |  |  |  | filename => File::Basename::fileparse( $file ), | 
| 140 |  |  |  |  |  |  | }; | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | # emule doesn't escape #[]@$&+,;= | 
| 143 | 0 |  |  |  |  | 0 | $self -> {escaped_filename} = URI::Escape::uri_escape_utf8( $self -> {filename}, '^A-Za-z0-9\-_.!~*\'()#&+,;=' ); | 
| 144 |  |  |  |  |  |  | # []@$ | 
| 145 | 0 |  |  |  |  | 0 | $self -> {escaped_filename} =~ s/%5B/[/g; | 
| 146 | 0 |  |  |  |  | 0 | $self -> {escaped_filename} =~ s/%5D/]/g; | 
| 147 | 0 |  |  |  |  | 0 | $self -> {escaped_filename} =~ s/%40/\@/g; | 
| 148 | 0 |  |  |  |  | 0 | $self -> {escaped_filename} =~ s/%24/\$/g; | 
| 149 |  |  |  |  |  |  | # hashes. step 1 | 
| 150 | 0 |  |  |  |  | 0 | my @aich_tree; | 
| 151 |  |  |  |  |  |  | { | 
| 152 | 0 |  |  |  |  | 0 | my $base_blocks = sprintf("%d", $self -> {size} / CHUNK_SIZE); | 
| 153 | 0 | 0 |  |  |  | 0 | -- $base_blocks if $self -> {size} == $base_blocks * CHUNK_SIZE; | 
| 154 | 0 |  |  |  |  | 0 | &_define_base_trees_orientation( 'l', \ @aich_tree, 0, $base_blocks ); | 
| 155 |  |  |  |  |  |  | } | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | { | 
| 158 | 0 | 0 |  |  |  | 0 | open my $f, '<', $filename_to_access | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 159 |  |  |  |  |  |  | or die sprintf( 'cannot open %s for reading: %s', | 
| 160 |  |  |  |  |  |  | $file, | 
| 161 |  |  |  |  |  |  | $!, | 
| 162 |  |  |  |  |  |  | ); | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 0 |  |  |  |  | 0 | binmode $f; | 
| 165 | 0 |  |  |  |  | 0 | my ($t, $readed_bytes); | 
| 166 | 0 |  |  |  |  | 0 | my $md4 = Digest::MD4 -> new; | 
| 167 | 0 |  |  |  |  | 0 | while (defined($readed_bytes = read $f, $t, CHUNK_SIZE)) { | 
| 168 | 0 |  |  |  |  | 0 | $md4 -> add($t); | 
| 169 | 0 |  |  |  |  | 0 | $self -> {hash} .= $md4 -> clone -> digest; | 
| 170 | 0 |  |  |  |  | 0 | push @{$self -> {p}}, uc $md4 -> hexdigest; | 
|  | 0 |  |  |  |  | 0 |  | 
| 171 | 0 | 0 |  |  |  | 0 | if ($readed_bytes) { | 
| 172 | 0 |  |  |  |  | 0 | my $pos = 0; | 
| 173 | 0 |  |  |  |  | 0 | my @t_sha1; | 
| 174 | 0 |  |  |  |  | 0 | while ($pos < $readed_bytes) { | 
| 175 | 0 |  |  |  |  | 0 | push @t_sha1, Digest::SHA::sha1( substr( $t, $pos, BLOCK_SIZE ) ); | 
| 176 | 0 |  |  |  |  | 0 | $pos += BLOCK_SIZE; | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  | # sha1 for chunk | 
| 179 | 0 |  |  |  |  | 0 | &_get_root_hash( $aich_tree[ $#{ $self -> {p} } ], | 
|  | 0 |  |  |  |  | 0 |  | 
| 180 |  |  |  |  |  |  | \ @t_sha1, | 
| 181 |  |  |  |  |  |  | 0, | 
| 182 |  |  |  |  |  |  | $#t_sha1 | 
| 183 |  |  |  |  |  |  | ); | 
| 184 | 0 |  |  |  |  | 0 | $aich_tree[$#{$self -> {p}}] = $t_sha1[0]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 185 |  |  |  |  |  |  | } | 
| 186 | 0 | 0 |  |  |  | 0 | last if $readed_bytes != CHUNK_SIZE; | 
| 187 |  |  |  |  |  |  | } | 
| 188 | 0 |  |  |  |  | 0 | close $f; | 
| 189 |  |  |  |  |  |  | return undef unless defined $readed_bytes | 
| 190 | 0 | 0 | 0 |  |  | 0 | && $self -> {size} == $#{$self -> {p}} * CHUNK_SIZE + $readed_bytes; | 
|  | 0 |  |  |  |  | 0 |  | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | # hashes. step 2 | 
| 194 | 0 | 0 |  |  |  | 0 | if (@{$self -> {p}} == 1) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 195 | 0 |  |  |  |  | 0 | $self -> {hash} = $self -> {p}[0]; | 
| 196 |  |  |  |  |  |  | } else { | 
| 197 | 0 |  |  |  |  | 0 | $self -> {hash} = uc Digest::MD4::md4_hex( $self -> {hash} ); | 
| 198 |  |  |  |  |  |  | } | 
| 199 |  |  |  |  |  |  | # aich hashset | 
| 200 | 0 |  |  |  |  | 0 | &_get_root_hash( 'l', | 
| 201 |  |  |  |  |  |  | \ @aich_tree, | 
| 202 |  |  |  |  |  |  | 0, | 
| 203 |  |  |  |  |  |  | $#aich_tree | 
| 204 |  |  |  |  |  |  | ); | 
| 205 | 0 |  |  |  |  | 0 | $self -> {aich} = _encode_base32( $aich_tree[ 0 ] ); | 
| 206 | 0 |  |  |  |  | 0 | $self -> {reliable} = 1; | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 0 | 0 |  |  |  | 0 | if (ref $either) { | 
| 209 | 0 |  |  |  |  | 0 | %$either = %$self; | 
| 210 | 0 |  |  |  |  | 0 | 1; | 
| 211 |  |  |  |  |  |  | } else { | 
| 212 | 0 |  |  |  |  | 0 | bless $self, $either; | 
| 213 |  |  |  |  |  |  | } | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | =head2 from_link | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | Can be called as class or object method: | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | my $tl = Ed2k_link -> from_link( 'ed2k://|file|eMule0.49c.zip|2868871|0F88EEFA9D8AD3F43DABAC9982D2450C|/' ) | 
| 221 |  |  |  |  |  |  | or die 'incorrect link!'; | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | $t1 = from_link( 'ed2k://|file|eMule0.49c-Sources.zip|5770302|195B6D8286BF184C3CC0665148D746CF|/' ) | 
| 224 |  |  |  |  |  |  | or die 'incorrect link!'; | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | Takes mandatory (filename/size/hash) and optional (AICH hash, complete hashset) fields from the link. | 
| 227 |  |  |  |  |  |  | Checks some correctness of fields (acceptable symbols, length, ...). | 
| 228 |  |  |  |  |  |  | If link in parameter has complete hashset, checks compliance between hash and complete hashset. | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | In case of any incorrectness returns undef and object doesn't hold any link information. | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | If link in parameter has AICH and/or complete hashset, sets Reliable flag to false. Otherwise it's true. | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | =cut | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | sub from_link { | 
| 237 | 1 |  |  | 1 | 1 | 81367 | my $either = shift; | 
| 238 | 1 | 50 |  |  |  | 5 | %$either = () if ref $either; | 
| 239 | 1 |  |  |  |  | 2 | my $link = shift; | 
| 240 | 1 | 50 |  |  |  | 3 | return undef unless defined $link; | 
| 241 | 1 | 50 |  |  |  | 11 | return undef unless $link =~ m#^ed2k://\|file\|([\d\D]+?)\|(\d+)\|([\da-f]{32})\|#i; | 
| 242 | 1 |  |  |  |  | 8 | my $self = { escaped_filename => $1, | 
| 243 |  |  |  |  |  |  | size => $2, | 
| 244 |  |  |  |  |  |  | hash => uc $3, | 
| 245 |  |  |  |  |  |  | filename => Encode::decode( 'UTF-8', URI::Escape::uri_unescape( $1 ) ), | 
| 246 |  |  |  |  |  |  | reliable => 1, | 
| 247 |  |  |  |  |  |  | }; | 
| 248 |  |  |  |  |  |  |  | 
| 249 | 1 |  |  |  |  | 208 | $link = "|$'"; | 
| 250 | 1 | 50 |  |  |  | 5 | return undef unless $self -> {size}; | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | # complete hashset | 
| 253 | 1 | 50 |  |  |  | 3 | if ($link =~ m/\|p=([\d\D]*?)\|/) { | 
| 254 | 0 |  |  |  |  | 0 | my $t = uc $1; | 
| 255 | 0 |  |  |  |  | 0 | $link = "|$`$'"; | 
| 256 | 0 | 0 |  |  |  | 0 | return undef unless $t =~ m/^([\dA-F]{32}(:[\dA-F]{32})*)$/; | 
| 257 |  |  |  |  |  |  |  | 
| 258 | 0 |  |  |  |  | 0 | my @t = split ':', $1; | 
| 259 | 0 |  |  |  |  | 0 | $t = sprintf("%d", $self -> {size} / CHUNK_SIZE); | 
| 260 | 0 | 0 |  |  |  | 0 | ++ $t if $self -> {size} >= $t * CHUNK_SIZE; | 
| 261 | 0 | 0 |  |  |  | 0 | return undef unless $t == @t; | 
| 262 |  |  |  |  |  |  |  | 
| 263 | 0 | 0 |  |  |  | 0 | if (@t == 1) { | 
| 264 | 0 | 0 |  |  |  | 0 | return undef unless $self -> {hash} eq $t[0]; | 
| 265 |  |  |  |  |  |  | } else { | 
| 266 | 0 |  |  |  |  | 0 | my $t = ''; | 
| 267 | 0 |  |  |  |  | 0 | foreach my $bh (@t) { | 
| 268 | 0 |  |  |  |  | 0 | $t .= chr(hex($&)) while $bh =~ m/../g; | 
| 269 |  |  |  |  |  |  | } | 
| 270 | 0 | 0 |  |  |  | 0 | return undef unless $self -> {hash} eq uc Digest::MD4::md4_hex( $t ); | 
| 271 | 0 |  |  |  |  | 0 | $self -> {reliable} = 0; | 
| 272 |  |  |  |  |  |  | } | 
| 273 | 0 |  |  |  |  | 0 | $self -> {p} = \@t; | 
| 274 |  |  |  |  |  |  | } | 
| 275 | 1 | 50 | 33 |  |  | 11 | $self -> {p}[0] = $self -> {hash} if $self -> {size} < CHUNK_SIZE && not exists $self -> {p}; | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | # aich | 
| 278 | 1 | 50 |  |  |  | 4 | if ($link =~ m/\|h=([\d\D]*?)\|/) { | 
| 279 | 0 |  |  |  |  | 0 | $self -> {aich} = uc $1; | 
| 280 | 0 |  |  |  |  | 0 | $link = "|$`$'"; | 
| 281 | 0 | 0 |  |  |  | 0 | return undef unless $self -> {aich} =~ m/^[A-Z2-7]{32}$/; | 
| 282 | 0 |  |  |  |  | 0 | $self -> {reliable} = 0; | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 1 | 50 |  |  |  | 3 | if (ref $either) { | 
| 286 | 0 |  |  |  |  | 0 | %$either = %$self; | 
| 287 | 0 |  |  |  |  | 0 | $either; | 
| 288 |  |  |  |  |  |  | } else { | 
| 289 | 1 |  |  |  |  | 4 | bless $self, $either; | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  | } | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  | =head2 ok | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | Instance only method.  Returns true if object was successfully created and holds all required fields; | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | &do_something() if $t1 -> ok; | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | =cut | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | sub ok { | 
| 302 | 1 | 50 |  | 1 | 1 | 551 | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 303 | 1 |  | 33 |  |  | 13 | return exists $instance -> {escaped_filename} && exists $instance -> {size} && exists $instance -> {hash}; | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | =head2 filename | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | Instance method.  Returns filename as character string: | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | print $t -> filename; | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | =cut | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | sub filename { | 
| 315 | 1 | 50 |  | 1 | 1 | 4 | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 316 | 1 |  |  |  |  | 5 | $instance -> {filename}; | 
| 317 |  |  |  |  |  |  | } | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | =head2 escaped_filename | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | Instance method.  Returns escaped filename (as in link); | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | print $t -> escaped_filename; | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | =cut | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | sub escaped_filename { | 
| 328 | 1 | 50 |  | 1 | 1 | 4 | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 329 | 1 |  |  |  |  | 4 | $instance -> {escaped_filename}; | 
| 330 |  |  |  |  |  |  | } | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | =head2 filesize | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | Instance method.  Returns filesize; | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | =cut | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | sub filesize { | 
| 339 | 1 | 50 |  | 1 | 1 | 5 | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 340 | 1 |  |  |  |  | 4 | $instance -> {size}; | 
| 341 |  |  |  |  |  |  | } | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | =head2 hash | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | Instance method.  Returns hash field from link; | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | =cut | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | sub hash { | 
| 350 | 0 | 0 |  | 0 | 1 |  | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 351 | 0 |  |  |  |  |  | $instance -> {hash}; | 
| 352 |  |  |  |  |  |  | } | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | =head2 has_complete_hashset | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | Instance method.  Returns true if object has complete hashset, false otherwise; | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | =cut | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | sub has_complete_hashset { | 
| 361 | 0 | 0 |  | 0 | 1 |  | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 362 | 0 | 0 |  |  |  |  | exists $instance -> {p} && @{$instance -> {p}}; | 
|  | 0 |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | =head2 complete_hashset | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | Instance method.  Returns complete hashset if object has it.  undef otherwise; | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | =cut | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | sub complete_hashset { | 
| 372 | 0 | 0 |  | 0 | 1 |  | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 373 |  |  |  |  |  |  | $instance -> has_complete_hashset ? | 
| 374 | 0 | 0 |  |  |  |  | join ':', @{$instance -> {p}} | 
|  | 0 |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | : undef; | 
| 376 |  |  |  |  |  |  | } | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | =head2 has_aich | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | Instance method.  Returns true if object has aich hash, false otherwise; | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | =cut | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | sub has_aich { | 
| 385 | 0 | 0 |  | 0 | 1 |  | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 386 | 0 |  |  |  |  |  | exists $instance -> {aich}; | 
| 387 |  |  |  |  |  |  | } | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | =head2 aich | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | Instance method.  Returns AICH hash if object has it.  undef otherwise; | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | =cut | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | sub aich { | 
| 396 | 0 | 0 |  | 0 | 1 |  | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 397 | 0 |  |  |  |  |  | $instance -> {aich}; | 
| 398 |  |  |  |  |  |  | } | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | =head2 link | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | Instance only method.  Returns string representation of link.  Can have parameter with options: | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | h - include AICH hash if available.  Recommended. | 
| 405 |  |  |  |  |  |  | p - include complete hashset if available. | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | my $link1 = $t -> link; | 
| 408 |  |  |  |  |  |  | my $link_with_aich = $t -> link( 'h' ); | 
| 409 |  |  |  |  |  |  | my $link_with_hashset = $t -> link( 'p' ); | 
| 410 |  |  |  |  |  |  | my $iron_link = $t -> link( 'hp' ); | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | =cut | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | sub link { | 
| 415 | 0 | 0 |  | 0 | 1 |  | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 416 | 0 |  |  |  |  |  | my $optional = shift; | 
| 417 | 0 | 0 |  |  |  |  | return undef unless $instance -> ok; | 
| 418 |  |  |  |  |  |  |  | 
| 419 | 0 |  |  |  |  |  | my @part = ( 'ed2k://|file', | 
| 420 |  |  |  |  |  |  | $instance -> escaped_filename, | 
| 421 |  |  |  |  |  |  | $instance -> filesize, | 
| 422 |  |  |  |  |  |  | $instance -> hash, | 
| 423 |  |  |  |  |  |  | ); | 
| 424 |  |  |  |  |  |  |  | 
| 425 | 0 | 0 |  |  |  |  | if ( defined $optional ) { | 
| 426 |  |  |  |  |  |  | # complete hashset | 
| 427 | 0 | 0 | 0 |  |  |  | push @part, | 
|  |  |  | 0 |  |  |  |  | 
| 428 |  |  |  |  |  |  | 'p=' . $instance -> complete_hashset | 
| 429 |  |  |  |  |  |  | if index( $optional, 'p' ) != -1 | 
| 430 |  |  |  |  |  |  | && $instance -> filesize >= CHUNK_SIZE | 
| 431 |  |  |  |  |  |  | && $instance -> has_complete_hashset; | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | # aich hashset | 
| 434 | 0 | 0 | 0 |  |  |  | push @part, | 
| 435 |  |  |  |  |  |  | 'h=' . $instance -> aich | 
| 436 |  |  |  |  |  |  | if index( $optional, 'h' ) != -1 | 
| 437 |  |  |  |  |  |  | && $instance -> has_aich; | 
| 438 |  |  |  |  |  |  | } | 
| 439 |  |  |  |  |  |  |  | 
| 440 | 0 |  |  |  |  |  | join '|', @part, '/'; | 
| 441 |  |  |  |  |  |  | } | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | =head2 is_reliable | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | Instance method.  Returns true if object is reliable, false otherwise; | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | =cut | 
| 448 |  |  |  |  |  |  |  | 
| 449 |  |  |  |  |  |  | sub is_reliable { | 
| 450 | 0 | 0 |  | 0 | 1 |  | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 451 | 0 |  |  |  |  |  | $instance -> {reliable}; | 
| 452 |  |  |  |  |  |  | } | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | =head2 set_reliable | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | Instance method.  Sets Reliable flag for object.  Use it very carefully, or you could end up with fake link | 
| 457 |  |  |  |  |  |  | that doesn't reference any file and you won't be able to download anything with them. | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | Carefully means: you got string link from someone, who you trust.  Or you previously created it from file | 
| 460 |  |  |  |  |  |  | by yourself and saved somethere and now you're reading those links from file of database. | 
| 461 |  |  |  |  |  |  | Such usage of this method is appropriated; | 
| 462 |  |  |  |  |  |  |  | 
| 463 |  |  |  |  |  |  | =cut | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | sub set_reliable { | 
| 466 | 0 | 0 |  | 0 | 1 |  | ref(my $instance = shift) or Carp::croak "class usage! need to be instance usage"; | 
| 467 | 0 |  |  |  |  |  | $instance -> {reliable} = 1; | 
| 468 |  |  |  |  |  |  | } | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | =head2 equal | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | Class only method. | 
| 473 |  |  |  |  |  |  | Compares two Ed2k_link objects by complex rules.  Returns true if they point to the same file. | 
| 474 |  |  |  |  |  |  | Could fill some fields of one object with other's objects fields.  Also can set Reliable flag. | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | print "hey! they are the same!" if Ed2k_link -> equal($t1, $t2); | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | =cut | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | sub equal { | 
| 481 | 0 |  |  | 0 | 1 |  | my $class = shift; | 
| 482 | 0 | 0 |  |  |  |  | return undef unless @_ == 2; | 
| 483 | 0 |  |  |  |  |  | my $one = shift; | 
| 484 | 0 |  |  |  |  |  | my $two = shift; | 
| 485 | 0 |  | 0 |  |  |  | my $res = $one -> ok && $two -> ok && $one -> filesize == $two -> filesize && $one -> hash eq $two -> hash; | 
| 486 | 0 | 0 |  |  |  |  | return undef unless $res; | 
| 487 | 0 | 0 | 0 |  |  |  | $res = $one -> complete_hashset eq $two -> complete_hashset | 
| 488 |  |  |  |  |  |  | if $one -> has_complete_hashset && $two -> has_complete_hashset; | 
| 489 | 0 | 0 |  |  |  |  | return undef unless $res; | 
| 490 | 0 | 0 | 0 |  |  |  | $res = $one -> aich eq $two -> aich | 
| 491 |  |  |  |  |  |  | if $one -> has_aich && $two -> has_aich; | 
| 492 | 0 | 0 |  |  |  |  | return undef unless $res; | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | # cases with copying complete hash or aich and setting reliable flag | 
| 495 | 0 | 0 | 0 |  |  |  | if ($one -> is_reliable && $two -> is_reliable) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 496 | 0 | 0 | 0 |  |  |  | if ($one -> has_complete_hashset && !$two -> has_complete_hashset) { | 
|  |  | 0 | 0 |  |  |  |  | 
| 497 | 0 |  |  |  |  |  | $two -> {p} = $one -> {p}; | 
| 498 |  |  |  |  |  |  | } elsif (!$one -> has_complete_hashset && $two -> has_complete_hashset) { | 
| 499 | 0 |  |  |  |  |  | $one -> {p} = $two -> {p}; | 
| 500 |  |  |  |  |  |  | } | 
| 501 | 0 | 0 | 0 |  |  |  | if ($one -> has_aich && !$two -> has_aich) { | 
|  |  | 0 | 0 |  |  |  |  | 
| 502 | 0 |  |  |  |  |  | $two -> {aich} = $one -> {aich}; | 
| 503 |  |  |  |  |  |  | } elsif (!$one -> has_aich && $two -> has_aich) { | 
| 504 | 0 |  |  |  |  |  | $one -> {aich} = $two -> {aich}; | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  | } elsif ($one -> is_reliable) { | 
| 507 | 0 |  |  |  |  |  | my $t = 0; | 
| 508 | 0 | 0 |  |  |  |  | if ($one -> has_complete_hashset) { | 
| 509 | 0 |  |  |  |  |  | ++ $t; | 
| 510 | 0 |  |  |  |  |  | $two -> {p} = $one -> {p}; | 
| 511 |  |  |  |  |  |  | } | 
| 512 | 0 | 0 |  |  |  |  | if ($one -> has_aich) { | 
| 513 | 0 |  |  |  |  |  | ++ $t; | 
| 514 | 0 |  |  |  |  |  | $two -> {aich} = $one -> {aich}; | 
| 515 |  |  |  |  |  |  | } | 
| 516 | 0 | 0 |  |  |  |  | -- $t if $two -> has_complete_hashset; | 
| 517 | 0 | 0 |  |  |  |  | -- $t if $two -> has_aich; | 
| 518 | 0 | 0 |  |  |  |  | $two -> set_reliable if $t >= 0; | 
| 519 |  |  |  |  |  |  | } elsif ($two -> is_reliable) { | 
| 520 | 0 |  |  |  |  |  | my $t = 0; | 
| 521 | 0 | 0 |  |  |  |  | if ($two -> has_complete_hashset) { | 
| 522 | 0 |  |  |  |  |  | ++ $t; | 
| 523 | 0 |  |  |  |  |  | $one -> {p} = $two -> {p}; | 
| 524 |  |  |  |  |  |  | } | 
| 525 | 0 | 0 |  |  |  |  | if ($two -> has_aich) { | 
| 526 | 0 |  |  |  |  |  | ++ $t; | 
| 527 | 0 |  |  |  |  |  | $one -> {aich} = $two -> {aich}; | 
| 528 |  |  |  |  |  |  | } | 
| 529 | 0 | 0 |  |  |  |  | -- $t if $one -> has_complete_hashset; | 
| 530 | 0 | 0 |  |  |  |  | -- $t if $one -> has_aich; | 
| 531 | 0 | 0 |  |  |  |  | $one -> set_reliable if $t >= 0; | 
| 532 |  |  |  |  |  |  | } | 
| 533 |  |  |  |  |  |  |  | 
| 534 | 0 |  |  |  |  |  | $res; | 
| 535 |  |  |  |  |  |  | } | 
| 536 |  |  |  |  |  |  |  | 
| 537 |  |  |  |  |  |  | 1; | 
| 538 |  |  |  |  |  |  | __END__ |