<? php /* 功能描述:Torrent文件解码器,使用数组表示文件结构修改时间:2006-10-21 01:39修改者:fishtrees版本: v0.2备注:与v0.1相比使用了更简单的数据结构 */ // if (eregi("bdecoder.php",$_SERVER['PHP_SELF'])) die("You can't access this file directly"); include_once ( " debug.fun.php " ); class Bdecoder{ // 定义类型常量 public static $STR = " s " ; public static $INT = " i " ; public static $LIST = " l " ; public static $DIC = " d " ; // 控制模块 //<String> $str torrent文件原始串 public function BDecode( $str ) { $root = $this -> BDec( $str ); // 开始解码 if ( $root !== false && $root !== null ){ return $root ; } else { return false ; } } // 解码器 //<String> &$str torrent文件原始串 private function BDec( & $str ) { // 解码类型选择 if ( preg_match ( ' /^(d+):/ ' , $str )) // 字符串类型 return $this -> BDec_string( $str ); elseif ( preg_match ( ' /^i(d+)e/ ' , $str )) // 整数类型 return $this -> BDec_integer( $str ); elseif ( $str [ 0 ] == " l " ) // 表类型 return $this -> BDec_list( $str ); elseif ( $str [ 0 ] == " d " ) // 字典类型 return $this -> BDec_dictionary( $str ); else trigger_error ( " Error: " . htmlspecialchars ( substr ( $str , 0 , 50 )) , E_USER_WARNING ); // 出错 return null ; } // 解码字符串类型 //<String> $str torrent文件原始串 private function BDec_string( & $str ) { if ( ! preg_match ( ' /^(d+):/ ' , $str , $m )){ // 判断原始串第一个编码模式是否是字符串类型编码模式后截取该模式 //该类型编码模式=N:长度为N的字符串 return false ; } $l = $m [ 1 ]; // 取得要提取的字符串长度 $pl = strlen ( $l ) + 1 ; // 取得开始位置 $v = substr ( $str , $pl , $l ); // 取得要提取字符串 $str = substr ( $str , $pl + $l ); // 从原始串中删除已经提取的字符串 //$rv=array('value'=>$v,'btdatatype'=>self::$STR);//构造返回值 $rv = self :: $STR . $v ; // 构造返回值 return $rv ; } // 解码数据项名称,字符串类型 //<String> $str torrent文件原始串 private function BDec_name( & $str ) { // 同解码字符串类型,只是不构造返回值 if ( ! preg_match ( ' /^(d+):/ ' , $str , $m )) return false ; $l = $m [ 1 ]; $pl = strlen ( $l ) + 1 ; $v = substr ( $str , $pl , $l ); $str = substr ( $str , $pl + $l ); return $v ; } // 解码整数类型 //<String> $str torrent文件原始串 private function BDec_integer( & $str ) { if ( ! preg_match ( ' /^i(d+)e/ ' , $str , $m )) return false ; // 判断原始串第一个编码模式是否是整数类型编码模式后截取该模式 //整数类型编码模式=i值e $v = $m [ 1 ]; // 取得整数值 if ( $v === " -0 " OR ( $v [ 0 ] == " 0 " AND strlen ( $v ) != 1 )) return false ; // 判断整数值是否合法 $str = substr ( $str , strlen ( $v ) + 2 ); // $rv=array('value'=>$v,'btdatatype'=>self::$INT);//构造返回值 $rv = self :: $INT . $v ; // 构造返回值 return $rv ; } // 解码表类型 //<String> $str torrent文件原始串 private function BDec_list( & $str ) { if ( $str [ 0 ] != " l " ) // 判断原始串第一个编码模式是否是表类型编码模式 //表类型编码模式=l任意类型的编码模式e return false ; $str = substr ( $str , 1 ); // 将原始串中的第1个代表表模式开始的字符去掉,准备开始提取表模式中的内容 //初始化返回值 $children = array (); // $children['value']=array(); $i = 0 ; do { if ( $str [ 0 ] == " e " ) break ; // 如果当前原始串是表模式的结束字符。则停止提取 $get = $this -> BDec( $str ); // 提取表模式中包含的其他模式的所代表的内容 //构造返回值 $children [] = $get ; unset ( $get ); } while ( true ); $str = substr ( $str , 1 ); // 在原始串中删除当前解码的表模式的结束字符 $children [ ' btdatatype ' ] = self :: $LIST ; return $children ; } // 解码字典类型 //<String> $str torrent文件原始串 private function BDec_dictionary( & $str ) { if ( $str [ 0 ] != " d " ) return false ; // 判断原始串第一个编码模式是否是字典类型编码模式 //表类型编码模式=d任意类型的编码模式e $str = substr ( $str , 1 ); // 将原始串中的第1个代表字典模式开始的字符去掉,准备开始提取这个字典模式中的内容 //初始化返回值 $children = Array (); do { if ( $str [ 0 ] == " e " ) break ; // 如果当前原始串是字典模式的结束字符。则停止提取 $name = $this -> BDec_string( $str ); // 取得字典中数据项的名称,如info unset ( $get ); $get = $this -> BDec( $str ); // 提取当前字典模式中所包含的其他编码模式的内容 //构造返回值 $children [ $name ] = $get ; } while ( true ); $str = substr ( $str , 1 ); // 在原始串中删除当前解码的字典模式的结束字符 $children [ ' btdatatype ' ] = self :: $DIC ; return $children ; } /* SYNTAXArray( [sannounce] => shttp://202.202.120.230:6969/announce [screated by] => sBitComet/0.62 [screation date] => i1159691884 [sencoding] => sGBK [sinfo] => Array ( [slength] => i152788 [sname] => sBt-protocol-wiki - 維基百科.mht [sname.utf-8] => sBt-protocol-wiki .mht [spiece length] => i32768 [spieces] => [btdatatype] => d ) [snodes] => Array ( [0] => Array ( [0] => s220.139.90.42 [1] => i18974 [btdatatype] => l ) [1] => Array ( [0] => s84.43.40.27 [1] => i25632 [btdatatype] => l ) [2] => Array ( [0] => s218.254.81.203 [1] => i7844 [btdatatype] => l ) [3] => Array ( [0] => s220.160.218.216 [1] => i29754 [btdatatype] => l ) [4] => Array ( [0] => s68.62.155.116 [1] => i25167 [btdatatype] => l ) [5] => Array ( [0] => s85.84.133.233 [1] => i15547 [btdatatype] => l ) [6] => Array ( [0] => s85.75.114.252 [1] => i6881 [btdatatype] => l ) [7] => Array ( [0] => s213.180.110.115 [1] => i26874 [btdatatype] => l ) [8] => Array ( [0] => s203.59.104.81 [1] => i26671 [btdatatype] => l ) [9] => Array ( [0] => s84.3.226.219 [1] => i23422 [btdatatype] => l ) [btdatatype] => l ) [spublisher] => sfishtrees [spublisher-url] => shttp://bbs.swnu.edu.cn/show.php?area=14&aid=1190 [spublisher-url.utf-8] => shttp://bbs.swnu.edu.cn/show.php?area=14&aid=1190 [spublisher.utf-8] => sfishtrees [btdatatype] => d) */ // 在已经解码的Torrent中查找指定的数据项名称是否存在 返回值<boolean> //<Array>$torrent 已经解码的Torrent文件 //<String>$query 要查询的路径,如/info,第一个'/'代表根路径 public function entry_exists( $torrent , $query ) { if ( ! preg_match_all ( " /([^/]+)/ " , $query , $matches , PREG_SET_ORDER)){ // 判断查询路径格式后截取各个路径名 trigger_error ( " Invalid Query " , E_USER_WARNING ); return false ; } // 按路径查找是否存在要查找的路径 for ( $i = 0 ; $i < count ( $matches ); $i ++ ){ if ( $i == 0 ){ // 第一次从根路径开始查找 $flag = $this -> chkPath( $torrent , ' s ' . $matches [ $i ][ 0 ]); // 判断父路径(即根路径)下的第一个路径名是否存在 if ( $flag == false ){ return $flag ; } $parent = $torrent [ ' s ' . $matches [ $i ][ 0 ]]; // 设置下一次开始查找的父路径 } else { $flag = $this -> chkPath( $parent , ' s ' . $matches [ $i ][ 0 ]); // 判断父路径下的第一个路径名是否存在 if ( $flag == false ){ return $flag ; } $parent = $parent [ ' s ' . $matches [ $i ][ 0 ]]; // 设置下一次开始查找的父路径 } } return $flag ; } // 在已经解码的Torrent中取得指定的数据项 返回值<Array> //<Array>$torrent 已经解码的Torrent文件 //<String>$query 要查询的路径,如/info,第一个'/'代表根路径 public function entry_get( $torrent , $query ) { if ( ! preg_match_all ( " /([^/]+)/ " , $query , $matches , PREG_SET_ORDER)){ // 判断查询路径格式后截取各个路径名 trigger_error ( " Invalid Query " , E_USER_WARNING ); return false ; } if ( ! $this -> entry_exists( $torrent , $query )) return false ; for ( $i = 0 ; $i < count ( $matches ); $i ++ ){ if ( $i == 0 ){ // 第一次从根路径开始查找 $get = $torrent [ ' s ' . $matches [ $i ][ 0 ]]; // 取得父路径(即根路径)中的第一个路径所包含的数据项 } else { $get = $get [ ' s ' . $matches [ $i ][ 0 ]]; // 从父路径(即上一次的路径)中取得当前路径所包含的数据项 } } return $get ; } // 在已经解码的Torrent中的单个路径中查找指定的数据项名称是否存在 返回值<boolean> //<Array>$arr 已经解码的Torrent文件的某个路径中的数据项集合 //<String>$path 要查询的单个路径,如info,深度为1 //注:判断返回值是否合法请用绝对等于,=== private function chkPath( $arr , $path ){ if (@ is_array ( $arr )){ if ( array_key_exists ( $path , $arr )){ return true ; } else { return false ; } } else { return false ; } }} // 测试 $str = file_get_contents ( ' d:/testfiles.torrent ' ); $dcoder = new Bdecoder(); $torrent = $dcoder -> BDecode( $str ); // debug_echo('解码后',$torrent); debug_echo( ' get ' , $isExist = $dcoder -> entry_get( $torrent , ' /info/files/ ' )); // debug_echo('是否存在',$isExist); ?>