In the previous part of this guide, we have seen how to run the XBTT tracker on our server and built a basic torrent download system with passkey support. Now we need a way to upload our torrents, so in this second part we’ll see how to create a fully working upload form featuring validation of metainfo .torrent files, including checks such as MIME type, private flag, announce URL, and torrent hash to detect duplicates.
A look at metafile specification
As we already know, a torrent file is a bencoded dictionary containing meta information such as torrent name, announce URL of the tracker, file list including names and sizes, creation date and other values. On wiki.theory.org there’s a full structure specification of torrent meta files, so be sure to check it out.
If we are building a private tracker, in the upload process we will need to check some values in order to validate the torrent file:
- • announce: string containing the announce URL of the tracker
- • private: an integer which, when set to 1, will prevent clients to obtain peers from sources other than the specified tracker (such as PEX or DHT).
- • files: list containing information about files. This allows us to check for a “Torrent downloaded from” text file (a la Demonoid.com)
In order to validate a torrent file, we need a bencode library. If
you’ve played around with the previous tutorial, you may already have
found your favourite one. Anyway, in code examples we will refer to this
extension: http://www.pear.php.net/package/File_Bittorrent2/
To use this, you will need to install PEAR on your system first. On
Debian (or derivatives such as Ubuntu) it’s a matter of a simple
command:
Now just type this to install the File_Bittorrent2 package:
If all went fine, our PHP scripts should now be able to access the functions of this library.
Setting up the form
For the sake of this tutorial, our upload form will be extremely simple
and will contain only the necessary fields. Obviously in a real world
form you’d want to include fields such as torrent name, description,
drop-down list of categories and so on.
Here’s the HTML code for our quick and simple form:
Obviously in the upload-torrent.php script we’re going to handle
validation, and if the file is valid, we will copy the uploaded file in
a folder on the server.
Once the upload is complete, we will have access to information about
the file through the $_FILES global. The first things we want to
check are the file’s extension and size.
define(‘MAX_FILE_SIZE’,1000000); // maximum size we
allow, in bytes
if($_FILES[‘torrent’][’size’] >= MAX_FILE_SIZE)
die(‘This torrent file is too big’);
$ext=pathinfo($_FILES[‘torrent’][‘name’],PATHINFO_EXTENSION);
if($ext!=‘.torrent’)
die(‘Invalid extension’);
?>
Some trackers let you upload torrents with other extensions such as txt or png to bypass ISP checks. If this is your intention, you may want to skip the extension validation or edit the last if statements to fit your needs.
PHP gives us information about the file MIME type, although this is actually provided by the browser, and this sounds unreliable. To check the MIME type on the server, we can use the Fileinfo extension. As of PHP 5.3.0, this is installed by default. If you use an older version, just install it with PEAR using this command:
Of course Fileinfo needs a handle to the file in order to check its
type. But, at this point, the file is in a temporary location on our
server, until we move it into another directory.
This location is stored in the tmp_name element of the file array.
define(‘TORRENT_MIMETYPE’,‘application/x-bittorrent’);
// check file mime type (must be application/x-bittorrent)
$handle = finfo_open(FILEINFO_MIME);
$mime_type =
finfo_file($handle,$_FILES[‘torrent’][‘tmp_name’]);
if($mime_type!=TORRENT_MIMETYPE)
die(‘Invalid MIME type: ‘.$mime_type);
?>
Now that we are sure we’re actually dealing with a torrent file, we are
ready to validate its content.
We’re going to use File_Bittorrent, so see the previous paragraph if
you haven’t installed it yet.
define(‘ANNOUNCE_URL’,‘http://example.com:2710/announce’);
$dec=new File_Bittorrent2_Decode;
$info=$dec->decodeFile($_FILES[‘torrent’][‘tmp_name’]);
// check if a torrent with this info_hash already exists
$exists=mysql_fetch_row(mysql_query(“SELECT *
FROM xbt_files WHERE
LOWER(hex(`info_hash`))=’{$info[‘info_hash’]}’”));
if(($exists))
die(‘This torrent has already been uploaded’);
// private tracker: the announce URL must match the one of our
tracker
if($info[‘announce’]!=ANNOUNCE_URL)
die(‘Sorry, but the announce URL must be
‘.ANNOUNCE_URL);
// private tracker: if we force the uploader to enable the
private flag, our tracker will be the only source for peers
if(!$dec->isPrivate())
die(‘This torrent is not private. Please set the
private flag.’);
?>
At this point we may want to do the “Torrent downloaded from” info-file check. We just loop through each file in the torrent until we have found a filename that matches our constant string. Also, we check for the file size.
define(‘INFOFILE_FILENAME’,‘Torrent downloaded from
example.com.txt’);
define(‘INFOFILE_CONTENT’,‘Torrent downloaded from
example.com‘);
$infofile_found=false;
foreach($info[‘files’] as $file)
{
if($file[‘filename’]==INFOFILE_FILENAME)
{
if($file[’size’]==strlen(INFOFILE_CONTENT))
{
$infofile_found=true;
break;
}
}
}
if($infofile_found===false)
die(‘This torrent does not contain our info-file.
Be sure to add it into the root of your torrent.’);
?>
Uploading and inserting into tracker database
Now we’re ready to move the torrent from its temporary location to a
directory where we will store all the uploaded files. Also, we will
insert a new record on the XBTT database to start tracking it.
Since we will need to easily access the torrent file in the future
(e.g. in details pages), we’re going to rename it after the torrent ID.
For example, if the torrent’s record index on the database is 1, the
filename will be “1.torrent”. In this way it will be easy to get
information from the file in PHP scripts given the torrent ID (this will
also keep the filesystem clean).
define(‘TORRENT_UPLOAD_PATH’,‘uploads’);
define(‘UPLOADED_TORRENT_EXTENSION’,‘.torrent’);
$filename=basename($_FILES[‘torrent’][‘name’]);
// get next auto_increment value in the files database
$row=mysql_fetch_array(mysql_query(“SHOW TABLE
STATUS LIKE ‘xbt_files’”));
$next_increment = $row[‘Auto_increment’];
// target path (torrent uploads directory + next id in files
table + torrent ext)
$target_path =
TORRENT_UPLOAD_PATH.‘/’.$next_increment.UPLOADED_TORRENT_EXTENSION;
// move the file from the temp location to the target path
if(($_FILES[‘torrent’][‘tmp_name’],
$target_path))
die(‘There was an error trying to upload your
torrent. Please try again!’);
// it’s time to add a record for the torrent into the database
if((“INSERT INTO xbt_files
(info_hash,mtime,ctime)
VALUES(X{$info[‘info_hash’]},UNIX_TIMESTAMP(),UNIX_TIMESTAMP())”))
{
die(‘There was an error trying to add your
torrent.’);
// delete the file
unlink($target_path);
}
// get the last inserted id
$torrent_id=mysql_insert_id();
// show a success message, maybe including an URL to a details
page using the torrent id
echo ‘Your torrent was uploaded successfully.’;
?>
Here we go. First, we store the target path of the file, which is composed by the torrent uploads path, the next ID in the files table (as we’ve chosen to rename torrent files using IDs) plus the torrent extension. Then we use the move_uploaded_file function to move the file to our target location.
Once this is done, we want to insert the torrent in the XBTT files
table, so that the tracker will recognize it when clients request it
(don’t forget that if the auto_register option is enabled, XBTT
will insert the record automatically for any torrent). Notice that we
delete the uploaded file if the insert query fails.
Keep in mind that after we have inserted the new torrent, we will have
to wait the XBTT db check interval time (15 seconds by default) for it
to be actually tracked.
NOTE: If our site handles user management, we may want to add the uploader’s user ID into the insert query. Of course you will need to add a new INT field in your users table.
Deleting the torrent
What if an user uploaded the wrong torrent and we want to provide a way
to delete it?
It’s not difficult at all. It’s just a matter of telling XBTT not to
track that specific torrent anymore. This is done setting the flags
field. Also, we will delete the torrent metafile from the filesystem.
The ID filename thing comes in handy for this purpose.
mysql_query(“UPDATE xbt_files SET flags=1 WHERE fid={$torrent_id}”); $file=TORRENT_UPLOAD_PATH.‘/’.$torrent_id.UPLOADED_TORRENT_EXTENSION;
if(file_exists($file))
unlink($file);
?>
In the next database update, XBTT should automatically remove the record and stop tracking the torrent.
Conclusion
In this second part of the series, we’ve seen how to build a basic torrent upload form, featuring various kinds of validation. Also, we’ve seen how to insert a torrent into the tracker database and how to properly remove it.
Stay tuned and see you in the next tutorial!
Jimmyy Says:
Thanks for the tutorial. Can’t wait for the next one!
Malvin Says:
Thanks. Hope these kiddos wont start making hundreds of these. Like they do with warez forums
zokocx Says:
Thx., this will be useful for my distributed operating system project, just to learn basic of PHP and how to put code all together .
Markus Butzer Says:
thank you extremely a great deal , this can be so excellent web-site. i love your site!