doublecmd/plugins/wcx/torrent/src/TorrentFile.pas
2017-12-02 23:30:37 +00:00

386 lines
10 KiB
ObjectPascal

unit TorrentFile;
interface
uses
SysUtils, Contnrs, Hashes, Classes, BDecode;
type
TBitfield = array of boolean;
TTorrentPiece = class(TObject)
private
_Hash: String;
_HashBin: String;
_Valid: Boolean;
public
property Hash: String read _Hash;
property HashBin: String read _HashBin;
property Valid: Boolean read _Valid write _Valid;
constructor Create(Hash: String; HashBin:String; Valid: Boolean);
end;
TTorrentSubFile = class(TObject)
private
_Name: String;
_Path: String;
_Filename: String;
_Length: Int64;
_Offset: Int64;
_Left: Int64;
public
property Name: String read _Name write _Name;
property Path: String read _Path write _Path;
property Length: Int64 read _Length;
property Offset: Int64 read _Offset;
property Left: Int64 read _Left write _Left;
property Filename: String read _Filename write _Filename;
constructor Create(Name: String; Path: String; Length: Int64; Offset: Int64);
end;
TTorrentFile = class(TObject)
published
private
_Announce : String;
_Name : String;
_Comment : String;
_Length : Int64;
_CreationTime : Int32;
_Count : Integer;
_Err : TStringList;
_Tree : TObjectHash;
_SHA1Hash : String;
_HashBin : String;
_Multifile : Boolean;
_Files : TObjectList;
public
Pieces : array of TTorrentPiece;
PieceLength : Integer;
BackupTrackers : TStringList;
property Announce: String read _Announce write _Announce;
property Name: String read _Name write _Name;
property CreationTime: Int32 read _CreationTime write _CreationTime;
property Length: Int64 read _Length;
property Count: Integer read _Count;
property Tree: TObjectHash read _Tree;
property Errors: TStringList read _Err;
property Hash: String read _SHA1Hash;
property Comment: String read _Comment write _Comment;
property HashBin: String read _HashBin;
property Multifile: Boolean read _Multifile;
property Files: TObjectList read _Files write _Files;
procedure Clear();
function Load(Stream: TStream): Boolean;
procedure Save(Stream: TStream; Pieces : array of TTorrentPiece);
procedure Init(Announce, Name, Comment, HashBin:String; Length:Int64; Multifile:Boolean);
constructor Create();
destructor Destroy(); override;
end;
implementation
uses
DCDateTimeUtils, SHA1;
{ TTorrentSubFile }
constructor TTorrentSubFile.Create(Name, Path: String; Length: Int64; Offset: Int64);
begin
_Name := Name;
_Path := Path;
_Length := Length;
_Offset := Offset;
_Left := Length;
inherited Create();
end;
procedure TTorrentFile.Clear();
var
i : Integer;
begin
_Announce := '';
_Name := '';
_SHA1Hash := '';
_Length := 0;
_Count := 0;
_Files.Clear();
_Tree.Clear();
_Err.Clear();
for i := Low(Pieces) to High(Pieces) do FreeAndNil(Pieces[i]);
SetLength(Pieces,0);
_Multifile := False;
end;
constructor TTorrentFile.Create();
begin
_Files := TObjectList.Create();
_Tree := TObjectHash.Create();
_Err := TStringList.Create();
BackupTrackers := TStringList.Create;
inherited Create();
end;
destructor TTorrentFile.Destroy();
begin
Clear();
FreeAndNil(_Files);
FreeAndNil(_Tree);
FreeAndNil(_Err);
FreeAndNil(BackupTrackers);
inherited;
end;
procedure TTorrentFile.Init(Announce, Name, Comment, HashBin:String; Length:Int64; Multifile:Boolean);
begin
_Announce := Announce;
_Name := Name;
_Comment := Comment;
_HashBin := HashBin;
_Length := Length;
_Multifile := Multifile;
_CreationTime := DateTimeToUnixFileTime(Now);
end;
function TTorrentFile.Load(Stream: TStream): Boolean;
var
info, thisfile: TObjectHash;
files, path, backup, backup2: TObjectList;
fp, fn: String;
i, j, pcount: Integer;
sz, fs, fo: Int64;
digest: TSHA1Digest;
r: Boolean;
o: TObject;
s:string;
begin
Clear();
r := False;
sz := 0;
try
o := bdecodeStream(Stream);
if(Assigned(o)) then begin
_Tree := o as TObjectHash;
if(_Tree.Exists('announce')) then begin
_Announce := (_Tree['announce'] as TIntString).StringPart;
end else begin
_Err.Add('Corrupt File: Missing "announce" segment');
end;
if(_Tree.Exists('announce-list')) then begin
backup := _Tree['announce-list'] as TObjectList;
for i := 0 to backup.Count - 1 do begin
backup2 := (backup[i] as TObjectList);
for j:=0 to backup2.Count -1 do BackupTrackers.Add((backup2[j] as TIntString).StringPart);
end;
end;
if(_Tree.Exists('comment')) then begin
_Comment := (_Tree['comment'] as TIntString).StringPart;
end;
if(_Tree.Exists('creation date')) then begin
_CreationTime := (_Tree['creation date'] as TIntString).IntPart;
end;
if(_Tree.Exists('info')) then begin
info := _Tree['info'] as TObjectHash;
if(info.Exists('name')) then begin
_Name := (info['name'] as TIntString).StringPart;
if copy(_Name,system.length(_Name)-7,8)='.torrent' then
_Name:=copy(_Name,0,system.length(_Name)-8);
end else begin
_Err.Add('Corrupt File: Missing "info.name" segment');
end;
if(info.Exists('piece length')) then begin
PieceLength := (info['piece length'] as TIntString).IntPart;
end else begin
_Err.Add('Corrupt File: Missing "info.piece length" segment');
end;
{
if(info.Exists('pieces')) then begin
fp := (info['pieces'] as TIntString).StringPart;
pcount := System.Length(fp) div 20;
SetLength(Pieces,pcount);
for i := 0 to pcount - 1 do begin
s:=copy(fp,(i * 20) + 1,20);
Pieces[i] := TTorrentPiece.Create(bin2hex(s), s, False);
end;
end else begin
_Err.Add('Corrupt File: Missing "info.pieces" segment');
end;
}
if(info.Exists('length')) then begin // single-file archive
sz := (info['length'] as TIntString).IntPart;
_Count := 1;
_Files.Add(TTorrentSubFile.Create(_Name,'',sz,Int64(0)));
end else begin
if(info.Exists('files')) then begin
_Multifile := True;
files := info['files'] as TObjectList;
for i := 0 to files.Count - 1 do begin
thisfile := files[i] as TObjectHash;
if(thisfile.Exists('length')) then begin
fs := (thisfile['length'] as TIntString).IntPart;
end else begin
fs := Int64(0);
_Err.Add('Corrupt File: files[' + IntToStr(i) + '] is missing a "length" segment');
end;
fp := '';
fn := '';
if(thisfile.Exists('path')) then begin
path := thisfile['path'] as TObjectList;
for j := 0 to path.Count - 2 do
fp := fp + (path[j] as TIntString).StringPart + PathDelim;
if(path.Count > 0) then fn := (path[path.Count - 1] as TIntString).StringPart;
end else begin
_Err.Add('Corrupt File: files[' + IntToStr(i) + '] is missing a "path" segment');
end;
_Files.Add(TTorrentSubFile.Create(fn,fp,fs,sz));
sz := sz + fs;
end;
_Count := _Files.Count;
end else begin
_Err.Add('Corrupt File: Missing both "info.length" and "info.files" segments (should have one or the other)');
end;
end;
if(_Tree.Exists('_info_start') and _Tree.Exists('_info_length')) then begin
fo := Stream.Position;
Stream.Seek((_Tree['_info_start'] as TIntString).IntPart,soFromBeginning);
fs := (_Tree['_info_length'] as TIntString).IntPart;
SetLength(fp,fs);
Stream.Read(PChar(fp)^,fs);
digest := SHA1String(fp);
_SHA1Hash := SHA1Print(digest);
SetLength(_HashBin, 20);
Move(digest[0], _HashBin[1], 20);
Stream.Seek(fo,soFromBeginning);
end;
end else begin
_Err.Add('Corrupt File: Missing "info" segment');
end;
_Length := sz;
r := True;
end else begin
_Err.Add('Error parsing file; does not appear to be valid bencoded metainfo');
end;
except
_Err.Add('Something bad happened while trying to load the file, probably corrupt metainfo');
end;
Result := r;
end;
procedure TTorrentFile.Save(Stream: TStream; Pieces : array of TTorrentPiece);
var i:integer;
s,s2:string;
procedure WStrm(s:string);
begin
Stream.WriteBuffer(s[1],system.length(s));
end;
procedure WStrg(s:string);
var t:String;
begin
t:=inttostr(system.length(s))+':'+s;
WStrm(t);
end;
procedure WInt(i:int64);
begin
WStrm('i'); WStrm(IntToStr(i)); WStrm('e');
end;
begin
WStrm('d');
WStrg('announce'); WStrg(Announce);
if BackupTrackers.Count > 0 then
begin
WStrg('announce-list');
WStrm('l');
// Primary Tracker
WStrm('l');
WStrg(Announce);
WStrm('e');
// Backup Tracker
for i:=0 to BackupTrackers.Count-1 do
if BackupTrackers[i] <> Announce then
begin
WStrm('l');
WStrg(BackupTrackers[i]);
WStrm('e');
end;
WStrm('e');
end;
if Comment <> '' then
begin
WStrg('comment');
WStrg(comment);
end;
if Date <> 0 then
begin
WStrg('creation date');
WInt(CreationTime);
end;
WStrg('info'); WStrm('d');
if Multifile then
begin
WStrg('files'); WStrm('l');
for i:=0 to Files.Count-1 do
with (Files[i] as TTorrentSubFile) do
begin
WStrm('d');
WStrg('length');
WInt(Length);
WStrg('path');
WStrm('l');
if Path <> '' then
begin
s:=path;
repeat
if pos('\',s) <> 0 then
begin
s2:=copy(s,1,pos('\',s)-1); WStrg(s2);
Delete(s,1,pos('\',s));
end;
if (pos('\',s)=0) and (s <>'') then WStrg(s);
until pos('\',s)=0;
end;
WStrg(Name);
WStrm('e');
WStrm('e');
end;
WStrm('e');
end
else
begin
WStrg('length');
WInt(Length);
end;
WStrg('name');
WStrg(Name);
WStrg('piece length');
WInt(PieceLength);
WStrg('pieces');
WStrm(IntToStr((high(pieces)+1)*20));
WStrm(':');
for i:=0 to high(pieces) do WStrm(pieces[i].HashBin);
WStrm('e');
WStrm('e');
end;
constructor TTorrentPiece.Create(Hash, HashBin: String; Valid: Boolean);
begin
_Hash := Hash;
_HashBin := HashBin;
_Valid := Valid;
inherited Create();
end;
end.