torrent文件分析

torrent文件信息存储格式:

bencoding是一种以简洁格式指定和组织数据的方法。支持下列类型:字节串、整数、列表和字典。

1 字符串存储格式:  <字符串的长度>:<字符串的内容>
例如:    4:spam 表示spam, 2:ab 表示ab

2 数字的存储格式:  i<整数>e
例如:    i3e 表示整数3, i1024e 表示整数1024

3 列表的存储格式: l<子元素>e  其中:子元素可以是字符串,整数,列表和字典,或者是它们的组合体
例如:    l4:spam4:eggse    表示 [ "spam", "eggs" ]
        l3:logi32ee        表示 [ "log", 32 ]

4 字典的存储格式: d<<key><value><key><value><key><value>...<key><value>>e
其中:key只能是字符串类型,value则可以是字符串,整数,列表和字典,或者是它们的组合体,key和value必须是成对出现的
例如:    d3:cow3:moo4:spam4:eggse    表示 { "cow" => "moo", "spam" => "eggs" }
        d4:spaml1:a1:bee            表示 { "spam" => [ "a", "b" ] }
        d9:publisher3:bob4:spaml1:a1:be5:counti80ee  表示 { "publisher" => "bob", "spam" => [ "a", "b" ], "count" => 80 }

torrent文件的信息:

announce:                tracker服务器的URL(字符串)
announce-list(可选):    备用tracker服务器列表(列表)
creation date(可选):    种子创建的时间,Unix标准时间格式,从1970年1月1日 00:00:00到创建时间的秒数(整数)
comment(可选):            备注(字符串)
created by(可选):        创建人或创建程序的信息(字符串)

info:                一个字典结构,包含文件的主要信息,为分二种情况:单文件结构或多文件结构
    piece length:    每个块的大小,单位字节(整数)
    pieces:            每个块的20个字节的SHA1 Hash的值(二进制格式)

    单文件结构如下:
    name:            文件名(字符串)
    length:            文件长度,单位字节(整数)


    多文件结构如下:
    name:            目录名(字符串)
    files:            一个字典结构的列表,字典结构至少包含下面两个信息
        length:        文件长度,单位字节(整数)
        path:        文件的路径和名字,是一个列表结构,如"test"test.txt 列表为l4:test8test.txte

torrent文件解析的代码:

下面给出解析torrent文件C++示例代码:

//////////////////////////////////////////////////////////////////////////

// interfaceBencode.h

#pragma once

#include "interfaceString.h"


//////////////////////////////////////////////////////////////////////////
//    torrent信息存储文法分析
//    <content>    ::= <dict>
//    <dict>        ::= d<string><<string> | <int> | <dict> | <list>e
//    <list>        ::= l<string> | <int> | <dict> | <list>e
//    <string>    ::= <string length>:<string data>
//    <int>        ::= i<number>e
//////////////////////////////////////////////////////////////////////////

class INode
{
public:
    virtual ~INode(){}
    virtual bool encode(string& content) = 0;
};

class StringNode : public INode
{
public:
    virtual ~StringNode() {}
    virtual bool encode(string& content);

    string m_value;
};


class IntNode : public INode
{
public:
    virtual ~IntNode() {}
    virtual bool encode(string& content);

    int m_value;
};

class DictNode : public INode
{
public:
    virtual ~DictNode()
    {
        for (map<StringNode*, INode*>::iterator iter = m_map_nodes.begin(); iter != m_map_nodes.end(); ++iter)
        {
            delete iter->first;
            delete iter->second;
        }

        m_map_nodes.clear();
    }
    virtual bool encode(string& content);

    map<StringNode*, INode*> m_map_nodes;
};

class ListNode : public INode
{
public:
    virtual ~ListNode()
    {
        for (vector<INode*>::iterator iter = m_nodes.begin(); iter != m_nodes.end(); ++iter)
            delete *iter;

        m_nodes.clear();
    }
    virtual bool encode(string& content);

    vector<INode*> m_nodes;
};

bool StringNode::encode(string& content)
{
    if (content.size() < 3)
        return false;

    size_t pos = content.find(':', 0);
    if (pos == string::npos)
        return false;

    int count = 0;
    InterfaceString::to_number(content.substr(0, pos), count);

    m_value = content.substr(pos+1, count);
    content = content.erase(0, pos+1+count);

    return true;
}

bool IntNode::encode(string& content)
{
    if (content[0] != 'i')
    {
        // bad int node
        assert(false);
        return false;
    }

    size_t pos = content.find('e', 0);
    if (pos == string::npos)
        return false;

    string s_value = content.substr(1, pos-1);
    InterfaceString::to_number(s_value, m_value);

    content = content.erase(0, s_value.size()+2);
    return true;
}

bool DictNode::encode(string& content)
{
    if (content.empty())
        return false;

    if (content[0] != 'd')
    {
        // bad dict node
        assert(false);
        return false;
    }

    content = content.erase(0, 1);

    while (!content.empty())
    {
        StringNode* keyNode = new StringNode();
        keyNode->decode(content);

        if (content.empty())
            break;

        INode* valueNode = NULL;
        if (content[0] == 'l')
            valueNode = new ListNode();
        else if (content[0] == 'd')
            valueNode = new DictNode();
        else if (content[0] == 'i')
            valueNode = new IntNode();
        else
            valueNode = new StringNode();

        if (valueNode == NULL)
            return false;

        valueNode->encode(content);
        m_map_nodes[keyNode] = valueNode;

        if (content[0] == 'e')
        {
            content = content.erase(0, 1);
            break;
        }
    }
    return true;
}

bool ListNode::encode(string& content)
{
    if (content[0] != 'l')
    {
        // bad list node
        assert(false);
        return false;
    }

    content = content.erase(0, 1);

    while (!content.empty())
    {
        INode* valueNode = NULL;
        if (content[0] == 'l')
            valueNode = new ListNode();
        else if (content[0] == 'd')
            valueNode = new DictNode();
        else if (content[0] == 'i')
            valueNode = new IntNode();
        else
            valueNode = new StringNode();

        if (valueNode == NULL)
            return false;

        valueNode->encode(content);
        m_nodes.push_back(valueNode);

        if (content[0] == 'e')
        {
            content = content.erase(0, 1);
            break;
        }   
    }
    return true;
}

注:上述代码用到了数字跟字符串之间的转换,可自行实现。上述只是分析torrent文件里的信息,如果想得到全部信息可以这样调用:

string content = "d8:ann..........e"; // content表示torrent文件的内容

DictNode* pDictNode = new DictNode();

pDictNode->encode(content);

那么所有的信息都可以在pDictNode结点里找到了。

如果你想要得到torrent文件相关字段的信息,则还需要对上述代码进行封装,下面给出我封装过的类。

////////////////////////////////////////////////

// interfaceTorrentFile.h

#pragma once

// begin namespace core_common
namespace core_common   
{
class TorrentFile
{
public:
    struct files_t
    {
        string        file_path;
        uint64_t    file_size;
    };

    struct infos_t
    {
        uint64_t        piece_length;
        string            pieces;

        bool            is_dir;
        vector<files_t> files;
    };

    struct torrent_t
    {
        string            announce;
        vector<string>    announce_list;
        string            comment;
        string            create_by;
        uint64_t        create_data;
        string            encoding;
        infos_t            infos;
    };

public:

    ///将torrent文件的字符串转化为torrent结构
    static bool encode(const string& content, torrent_t& torrent);

    ///将torrent结构转化为torrent文件的字符串
    //static bool decode(const torrent_t& torrent, string& content);
};

};    // end namespace core_common

/////////////////////////////////////////////////

// interfaceTorrentFile.cpp

#include "interfaceBencode.h"

#include "InterfaceTorrent.h"

using namespace core_common;


INode* find_node(const map<StringNode*, INode*>& node_map, const string& key)
{
    for (map<StringNode*, INode*>::const_iterator iter = node_map.begin(); iter != node_map.end(); ++iter)
    {
        StringNode* pKeyNode = dynamic_cast<StringNode*>(iter->first);
        assert(pKeyNode != NULL);
        if (pKeyNode == NULL)
            return NULL;

        if ( pKeyNode->m_value == key )
            return iter->second;
    }

    return NULL;
}

string get_node_value(StringNode* strNode)
{
    return strNode == NULL ? "" : strNode->m_value;
}

uint64_t get_node_value(IntNode* intNode)
{
    return intNode == NULL ? 0 : intNode->m_value;
}

bool TorrentFile::encode(const string& torrent_content, torrent_t& torrent)
{   
    string content = torrent_content;

    DictNode* pDictNode = new DictNode();
    pDictNode->encode(content);

    torrent.create_by    = get_node_value( dynamic_cast<StringNode*>(find_node(pDictNode->m_map_nodes, "created by")) );    // 查找 created by
    torrent.create_data = get_node_value( dynamic_cast<IntNode*>(find_node(pDictNode->m_map_nodes, "creation date")) );    // 查找 creation date
    torrent.encoding    = get_node_value( dynamic_cast<StringNode*>(find_node(pDictNode->m_map_nodes, "encoding")) );    // 查找 encoding
    torrent.comment        = get_node_value( dynamic_cast<StringNode*>(find_node(pDictNode->m_map_nodes, "comment")) );    // 查找 comment
    torrent.announce    = get_node_value( dynamic_cast<StringNode*>(find_node(pDictNode->m_map_nodes, "announce")) );    // 查找 announce

    // 查找 announce-list
    {
        INode* pNode = find_node(pDictNode->m_map_nodes, "announce-list");
        if (pNode != NULL)
        {
            ListNode* pValueNode = dynamic_cast<ListNode*>(pNode);
            assert(pValueNode != NULL);
            if (pValueNode == NULL)
                return false;

            for (vector<INode*>::iterator iter_announce = pValueNode->m_nodes.begin(); iter_announce != pValueNode->m_nodes.end(); ++iter_announce)
                torrent.announce_list.push_back( get_node_value( dynamic_cast<StringNode*>(*iter_announce)) );
        }
    }

    // 查找 info
    INode* pNode = find_node(pDictNode->m_map_nodes, "info");
    if (pNode != NULL)
    {
        DictNode* pValueNode = dynamic_cast<DictNode*>(pNode);
        assert(pValueNode != NULL);
       
        torrent.infos.piece_length    = get_node_value( dynamic_cast<IntNode*>(find_node(pValueNode->m_map_nodes, "piece length")) );    // 查找 piece length
        torrent.infos.pieces            = get_node_value( dynamic_cast<StringNode*>(find_node(pValueNode->m_map_nodes, "pieces")) );        // 查找 piece

        // 查找 是否是目录
        INode* pSubNode = find_node(pValueNode->m_map_nodes, "files");
        if (pSubNode != NULL)
        {
            torrent.infos.is_dir = true;

            // 查找 目录名
            string dir_name = get_node_value( dynamic_cast<StringNode*>(find_node(pValueNode->m_map_nodes, "name")) );

            // 查找 子文件
            ListNode* pSubValueNode = dynamic_cast<ListNode*>(pSubNode);
            assert(pSubValueNode != NULL);
            if (pSubValueNode != NULL)
            {
                for (vector<INode*>::iterator iter_file = pSubValueNode->m_nodes.begin(); iter_file != pSubValueNode->m_nodes.end(); ++iter_file)
                {
                    DictNode* pDictNode = dynamic_cast<DictNode*>(*iter_file);
                    assert(pDictNode != NULL);
                    if (pDictNode != NULL)
                    {
                        files_t file;
                        file.file_size = get_node_value( dynamic_cast<IntNode*>(find_node(pDictNode->m_map_nodes, "length")) );

                        ListNode* pListNode = dynamic_cast<ListNode*>(find_node(pDictNode->m_map_nodes, "path"));
                        if (pListNode != NULL)
                        {
                            file.file_path = dir_name;
                            for (vector<INode*>::iterator iter_path = pListNode->m_nodes.begin(); iter_path != pListNode->m_nodes.end(); ++iter_path)
                            {
                                file.file_path += "//";
                                file.file_path += get_node_value( dynamic_cast<StringNode*>(*iter_path));
                            }
                        }

                        torrent.infos.files.push_back(file);
                    }
                }
            }
        }
        else
        {
            torrent.infos.is_dir = false;

            files_t file;
            file.file_size = get_node_value( dynamic_cast<IntNode*>(find_node(pValueNode->m_map_nodes, "length")) );
            file.file_path = get_node_value( dynamic_cast<StringNode*>(find_node(pValueNode->m_map_nodes, "name")) );

            torrent.infos.files.push_back(file);
        }
    }

    delete pDictNode;
    return true;
}

注1:该torrent_t结构只是官方发布的torrent文件可能包含的信息,如果有torrent文件有特殊结点也可自己定义,反正所有结点都能在DictNode中找到。

posted @ 2011-07-21 14:43  hnrainll  阅读(6702)  评论(1编辑  收藏  举报